Layer diagram
What each layer does
Domain is the core. Entities likeUser, Tenant, Subscription, Invitation, RefreshToken, and Notification live here. So do the Permissions constants, EmailTemplate enum, and all domain exceptions. Domain has no NuGet dependencies beyond logging abstractions. Nothing in Domain knows about the database, HTTP, or any external service.
Application defines what the system does. Every operation is either a Command (writes data) or a Query (reads data). Handlers contain the actual logic. Application defines interfaces (IApplicationDbContext, IEmailService, IStripeService, ICacheService, INotificationService) and the Infrastructure layer implements them. This inversion is what makes Infrastructure replaceable in tests.
Infrastructure implements the Application interfaces. ApplicationDbContext implements IApplicationDbContext. ResendEmailService implements IEmailService. StripeService implements IStripeService. All EF Core migrations live here. Infrastructure can reference NuGet packages freely because nothing depends on it except the Api host.
Api wires everything together. Controllers receive HTTP requests, immediately call MediatR.Send(), and map the result to an HTTP response. Middleware handles authentication, tenant resolution, and subscription status. The startup registers all dependencies.
CQRS with MediatR
Every operation flows through MediatR. Controllers never contain business logic.Result<T> type is a discriminated union — either success with a value, or failure with an error code and message. Controllers map results to HTTP responses in a single switch. No exceptions are thrown for expected business failures.
API response envelope
Every response from the API uses the same shape: Success:code field is a machine-readable string. See the Error Codes reference for every possible value.