Type to search pages and rules.

Browse pages

.NET Architecture

Architecture in a .NET backend is five projects with explicit composition roots.

Markdown5 provisions

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#

ruleguidance

Requirement 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

Text
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/
litespec/backend/dotnet/architecture#use-five-application-projects

Keep one Application assembly#

ruleguidance

Requirement 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.

litespec/backend/dotnet/architecture#keep-one-application-assembly

Keep implementation types internal#

ruleguidance

Requirement 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.

litespec/backend/dotnet/architecture#keep-implementation-types-internal

Conventions#

Keep composition in hosts#

conventionguidance

Default 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:

Text
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.

litespec/backend/dotnet/architecture#keep-composition-in-hosts

Use one public assembly marker per scanned project#

conventionguidance

Default 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.

litespec/backend/dotnet/architecture#use-one-public-assembly-marker-per-scanned-project

Verification#

IDMarkEvidence
litespec/backend/dotnet/architecture#use-five-application-projectsguidanceDoes the solution hold exactly the five baseline projects?
litespec/backend/dotnet/architecture#keep-one-application-assemblyguidanceDoes one Application project hold every message, handler, and module-owned port?
litespec/backend/dotnet/architecture#keep-implementation-types-internalguidanceIs every handler, validator, definition, and endpoint implementation internal and sealed?
litespec/backend/dotnet/architecture#keep-composition-in-hostsguidanceDoes all service registration live in hosts and host modules?
litespec/backend/dotnet/architecture#use-one-public-assembly-marker-per-scanned-projectguidanceDoes each scanned project expose exactly one public assembly marker?