Skip to content

0010. Clean Architecture for Backend and Feature-Based Architecture for Frontend

  • Status: Accepted
  • Date: 2026-10-02

Context

As BizOS grows across multi-tenant capabilities (IAM, Tenancy, CRM, Admin Operations, Billing), we need consistent, testable, and extractable structural patterns for both backend and frontend code in the monorepo: 1. On the backend, mixing HTTP handling, database queries, and business logic inside NestJS controllers or services violates separation of concerns and blocks module extraction (ADR-0008). 2. On the frontend, organizing files by technical category (controllers/, views/, models/) scatters a single capability across the codebase and increases cognitive load. 3. System operators and customer tenant users require strict identity and database separation.

Options considered

  1. Layered by Technical Type (MVC): Organizing folders into controllers/, services/, models/, views/. Simple initially, but creates tight coupling, slows down feature delivery, and makes extracting modules very painful.
  2. Clean Architecture (Backend) + Feature-Based Architecture (Frontend):
  3. Backend: Domain-Driven Clean Architecture per module (Domain ➔ Features/Use Cases ➔ Presentation ➔ Infrastructure).
  4. Frontend: Self-contained feature folders (pages, components, api, hooks, types, tests) with a shared headless design system (@bizos/shared-ui).
  5. Pure Microservices: Separate repositories or standalone services for each capability now. High operational complexity and premature overhead before product-market fit.

Decision

Adopt Clean Architecture within each backend module of our Modular Monolith, and Feature-Based Architecture within each frontend module and application shell:

  1. Backend:
  2. Domain (domain/): Pure entities, value objects, domain events, and repository interfaces (ports). Zero external framework dependencies.
  3. Application (features/<feature>/): Use case services, command/query handlers, and input validation DTOs. Depends only on domain interfaces via Dependency Inversion.
  4. Presentation (features/<feature>/): NestJS controllers, guards, and interceptors handling HTTP protocols, status codes, and cookies.
  5. Infrastructure (infrastructure/): Drizzle schemas, repository implementations, connection pools, and external service clients.
  6. Contract libraries (libs/api/contracts/<module>): Cross-module communication through abstract ports and integration events.
  7. Frontend:
  8. App Shells (apps/web, apps/admin): Thin shells managing HTML bootstrap, providers, routing frame, and theme. No direct business logic.
  9. Feature Slices (libs/web/<module>/src/lib/features/<feature>/): Self-contained vertical slices containing pages/, components/, api/, hooks/, types/, and co-located tests.
  10. Shared UI (libs/shared/ui): Headless, stateless design tokens and primitives (shadcn/ui base) composed by feature views.

Consequences

  • Business logic is completely isolated from database engines (PostgreSQL/Drizzle) and web frameworks (NestJS/Express), enabling fast, deterministic unit testing without database spin-up.
  • Any backend module can be extracted into an independent microservice later by implementing its contract port over HTTP/gRPC without modifying domain or application code.
  • Frontend features are easy to locate, modify, or retire without ripple effects.
  • Enforced at build time via Nx tags, ESLint dependency rules, and TypeScript project references.