| status | accepted |
|---|---|
| date | 2026-07-16 |
| decision-makers | Paul Custance |
Projects built from this template will grow domain logic that outlives their initial CRUD endpoints, and that logic must stay testable and independent of frameworks and infrastructure. How should the solution be structured so that business rules do not become entangled with HTTP, persistence, and other concerns that change for different reasons?
- Domain and application logic must be unit-testable without a database, web host, or other infrastructure.
- Infrastructure choices (ORM, database, auth provider) should be replaceable without rewriting business rules.
- The structure must be enforceable automatically, not just by convention, template consumers will not read a style guide.
- Newcomers should find one obvious place for each kind of code.
- Clean architecture with one project per layer
- Traditional N-tier layering (UI -> business logic -> data access)
- Vertical slice architecture in a single project
- Single-project minimal MVC with no enforced layering
Chosen option: "Clean architecture with one project per layer", because it is the only option that both inverts dependencies away from infrastructure and can be enforced mechanically at the assembly level.
The solution is split into five projects, with all dependencies pointing inward:
SharedKernel- base building blocks (Result,Error) with no dependencies.Domain- entities and business rules; depends only onSharedKernel.Application- command/query handlers for each use case, implementingICommandHandler/IQueryHandlerinterfaces. Handlers are decorated with cross-cutting concerns such as validation and logging. Depends onDomainandSharedKernel.Infrastructure- EF Core/PostgreSQL persistence, authentication, time - implementations of abstractions the inner layers define; depends onApplication.Web.Mvc- minimal controllers and composition root; referencesInfrastructureonly to wire dependency injection.
- Good, because handlers and domain types are testable in isolation; the architecture tests run with no infrastructure at all.
- Good, because layer violations fail the build rather than accumulating silently.
- Bad, because simple features carry ceremony: one endpoint typically means an endpoint class, a query/command, a handler, and a response type across two projects.
- Bad, because five projects is a heavier starting point than a single-project MVC for genuinely small services.
tests/SchoolAccount.ArchitectureTests encodes the dependency rules with NetArchTest (e.g.
Domain must not depend on Application, Application must not depend on
Infrastructure). These tests run in the Build workflow on every pull
request, so a violating change cannot merge cleanly.
- The Clean Architecture - Robert C. Martin's original description of the dependency rule.
- NetArchTest - the library used by the architecture tests.
- Related: ADR-0001 established this decision log.