Design Decisions
1. Decision Log Overview
This document captures the major architectural choices that shape TestoQA.
It focuses on:
- why decisions were made,
- what trade-offs were accepted,
- and what the system explicitly does not attempt to do.
This is not a changelog, but it should evolve when major architectural choices change.
2. Monolith Over Microservices
Decision
TestoQA is implemented as a single Next.js full-stack monolith.
Why
- reduces operational complexity early
- enables rapid iteration
- keeps boundaries clear in a single codebase
- avoids premature distribution concerns
Trade-offs
- tighter coupling between frontend and backend
- scaling requires discipline around module boundaries
- some workflows may eventually need background processing or separate services
3. App Router + Server-First Boundaries
Decision
Use Next.js App Router with:
- Server Components for server-first rendering
- Server Actions / Route Handlers as explicit system boundaries
Why
- encourages server-side correctness (auth, tenancy, authorization)
- reduces client complexity
- centralizes security enforcement
- improves performance by default via SSR patterns
Trade-offs
- requires clear boundary discipline
- can be easy to accidentally mix concerns without strong module rules
- caching behavior must be handled carefully in multi-tenant scenarios
4. Shared Database With Application-Level Tenancy
Decision
Use a single PostgreSQL database with a shared schema and enforce tenant isolation at the application level using projectId.
Why
- simplest operational model for early scale
- easier migrations than per-tenant schemas/databases
- supports cross-tenant admin operations when needed (explicitly)
Trade-offs
- tenant isolation is a correctness and security burden on the application
- caching requires careful scoping
- “forgotten filters” are a critical risk without structural safeguards
Tenancy enforcement rules are defined in
tenancy-model.mdx.
5. Prisma as the Only Data Access Path
Decision
Prisma is the only mechanism for database access and lives exclusively in repository/data-access layers.
Why
- consistent query and migration workflow
- type-safe access patterns
- centralizes tenant scoping and query hygiene
Trade-offs
- requires discipline to prevent Prisma usage from leaking into UI/boundary layers
- performance tuning depends on query review and indexing
6. Explicit Non-Goals
These are intentionally out of scope for the current architecture:
- microservices or distributed system design
- per-tenant databases/schemas
- complex background job processing (initially)
- extreme scale optimization before product/usage demands it
- client-authoritative state or offline-first semantics
7. Accepted Trade-offs
We accept:
- increased coupling in exchange for speed and clarity
- stronger reliance on review discipline and structural patterns
- operational simplicity over maximum isolation (no per-tenant DB)
- limited background processing initially (favor synchronous workflows)
8. Guiding Design Principles
- Clarity over cleverness
- Explicit over implicit
- Server/database as source of truth
- Fail closed for security
- Tenant safety as a first-class constraint
- Consistency across modules
9. Review Checklist (Design Decisions)
- Does this change preserve tenant isolation invariants?
- Does it respect module boundaries and dependency direction?
- Does it add complexity that should be deferred?
- If a core decision is changing, is this document updated?
Last updated on