Browse pages
.NET Architecture
Architecture in a .NET backend is five projects with explicit composition roots.
Intent#
Architecture in a .NET backend is five projects with explicit composition roots. The shared provisions live on the stack-neutral page (Backend architecture); this page holds what dotnet alone requires.
Standards#
Use five application projects#
ruleguidanceRequirement An application MUST use Domain, Application.Abstractions, Application, Infrastructure, and WebApi as its five baseline projects.
Rationale Application.Abstractions holds the contracts that more than one module names: the pipeline's own positions, the cross-module ports, the transport-neutral failures, and the published integration events. It references Domain and the required LiteBus module abstractions, and nothing else. No type in it can name a use case, a handler, or a module.
That refusal is the reason it is a project rather than a folder. A folder inside Application cannot decline what a developer puts in it, and the folder that tries becomes the place every unplaced type goes.
An implementation never belongs there. The project name states the test, and a type that cannot pass it belongs to a module under litespec/backend/architecture#place-a-type-at-the-lowest-folder-that-holds-its-consumers.
AppHost and ServiceDefaults support local hosting and diagnostics. They are hosts, not application layers.
This realizes litespec/backend/architecture#use-five-application-parts in the dotnet stack.
Example
apps/api/src/{ProjectName}.Domain/
apps/api/src/{ProjectName}.Application.Abstractions/
apps/api/src/{ProjectName}.Application/
apps/api/src/{ProjectName}.Infrastructure/
apps/api/src/{ProjectName}.WebApi/Keep one Application assembly#
ruleguidanceRequirement An application MUST place commands, queries, results, validators, handlers, reactions, and module-owned ports in one Application project, and cross-module contracts in Application.Abstractions.
Rationale Separate Write, Read, Contracts, and event-handler assemblies are outside this stack. The separation this stack keeps is the one Fowler describes as CQRS, which separates the command and query models rather than the assemblies holding them. Splitting assemblies buys a deployment boundary the stack does not use, and costs a project reference that every use case has to cross.
This realizes litespec/backend/architecture#keep-one-application-part in the dotnet stack.
Keep implementation types internal#
ruleguidanceRequirement A handler, validator, definition, persistence implementation, or endpoint implementation MUST be declared internal sealed.
Rationale Messages, results, assembly markers, and ports stay public only when another project uses them. Assembly scanning is not a reason to widen visibility.
This realizes litespec/backend/architecture#keep-implementation-types-hidden in the dotnet stack.
Conventions#
Keep composition in hosts#
conventionguidanceDefault Keep service registration in Program.cs and host registration modules.
Replacement A consumer replaces this default by recording an Exception for this provision, with reason and review date.
Rationale Domain and Application declare abstractions, so a registration call inside either inverts the dependency direction.
This realizes litespec/backend/architecture#keep-service-registration-in-hosts in the dotnet stack.
Example One registration entry point per outer layer has this shape:
InfrastructureServiceRegistration.AddInfrastructure(...)
WebApiServiceRegistration.AddWebApi(...)AddInfrastructure receives configuration and host environment values needed to bind and validate provider options. AddWebApi owns Problem Details, authentication, authorization, endpoint discovery, and OpenAPI. Program.cs keeps their call order visible.
Use one public assembly marker per scanned project#
conventionguidanceDefault Expose one public static assembly marker in each project that an approved scanner reads.
Replacement A consumer replaces this default by recording an Exception for this provision, with reason and review date.
Rationale The marker gives the scanner an assembly reference while the scanned handlers stay internal.
Verification#
| ID | Mark | Evidence |
|---|---|---|
| litespec/backend/dotnet/architecture#use-five-application-projects | guidance | Does the solution hold exactly the five baseline projects? |
| litespec/backend/dotnet/architecture#keep-one-application-assembly | guidance | Does one Application project hold every message, handler, and module-owned port? |
| litespec/backend/dotnet/architecture#keep-implementation-types-internal | guidance | Is every handler, validator, definition, and endpoint implementation internal and sealed? |
| litespec/backend/dotnet/architecture#keep-composition-in-hosts | guidance | Does all service registration live in hosts and host modules? |
| litespec/backend/dotnet/architecture#use-one-public-assembly-marker-per-scanned-project | guidance | Does each scanned project expose exactly one public assembly marker? |