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¶
- 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. - Clean Architecture (Backend) + Feature-Based Architecture (Frontend):
- Backend: Domain-Driven Clean Architecture per module (Domain ➔ Features/Use Cases ➔ Presentation ➔ Infrastructure).
- Frontend: Self-contained feature folders (pages, components, api, hooks, types, tests) with a shared headless design system (
@bizos/shared-ui). - 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:
- Backend:
- Domain (
domain/): Pure entities, value objects, domain events, and repository interfaces (ports). Zero external framework dependencies. - Application (
features/<feature>/): Use case services, command/query handlers, and input validation DTOs. Depends only on domain interfaces via Dependency Inversion. - Presentation (
features/<feature>/): NestJS controllers, guards, and interceptors handling HTTP protocols, status codes, and cookies. - Infrastructure (
infrastructure/): Drizzle schemas, repository implementations, connection pools, and external service clients. - Contract libraries (
libs/api/contracts/<module>): Cross-module communication through abstract ports and integration events. - Frontend:
- App Shells (
apps/web,apps/admin): Thin shells managing HTML bootstrap, providers, routing frame, and theme. No direct business logic. - Feature Slices (
libs/web/<module>/src/lib/features/<feature>/): Self-contained vertical slices containingpages/,components/,api/,hooks/,types/, and co-located tests. - 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.