Skip to Content
🎉 TestoQA 1.0 is released

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