.NET Architecture (DDD/CQRS)
Architecture using Domain-Driven Design (DDD) and CQRS patterns for clean, maintainable solutions.
Core Architectural Patterns
Domain-Driven Design (DDD)
Structure code around the business domain, not technical concerns.
-
Focus on domain first: Design models that reflect business semantics, not database structures.
-
Ubiquitous Language: Use business terminology consistently.
-
Bounded Contexts: Partition system into logical domains with clear boundaries.
For detailed guidance on Entities, Aggregates, Value Objects, Domain Services, and lifecycle patterns: → See references/ddd-concepts.md
CQRS (Command Query Responsibility Segregation)
Separate read and write operations at the architectural level.
-
Commands: Represent intent to change state → Return void or result object.
-
Queries: Request data with no side effects → Return DTOs.
-
Orchestration: Use MediatR to dispatch commands and queries to handlers.
For detailed command/query patterns, handler examples, and DTOs: → See references/cqrs-patterns.md
Layer Architecture
Four distinct layers with clear responsibilities:
Domain Layer
Pure business logic, no infrastructure dependencies.
-
Contains: Entities, Aggregates, Value Objects, Domain Services.
-
No I/O, database access, or external calls.
-
Encapsulates business rules and aggregate consistency.
public class Order // Aggregate Root { public Guid OrderId { get; private set; } private readonly List<OrderLine> _lines = new();
public void AddLine(Product product, int quantity)
{
if (Status != OrderStatus.Pending)
throw new InvalidOperationException();
_lines.Add(new OrderLine(product, quantity));
}
}
Application Layer
Orchestration and command/query handling.
-
Contains: Command/Query Handlers (CQRS).
-
Defines interfaces (repositories, external services).
-
Contains DTOs and mapping logic.
-
Handlers are thin; delegate to Domain.
public record CreateOrderCommand(Guid CustomerId, List<OrderLineDto> Lines) : IRequest<Guid>;
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, Guid> { public async Task<Guid> Handle(CreateOrderCommand command, CancellationToken ct) { var order = Order.CreateNew(customer, orderLines); await _orderRepository.AddAsync(order, ct); await _unitOfWork.SaveChangesAsync(ct); return order.OrderId; } }
Infrastructure Layer
Technical concerns and external integrations.
-
Implements repositories and external service interfaces.
-
Handles database persistence (EF Core).
-
Manages external integrations (APIs, brokers, caching).
-
Configures DI composition via extension methods.
Presentation Layer (API)
HTTP entry/exit points.
-
Controllers receive requests and delegate to MediatR.
-
No business logic in controllers.
-
Return DTOs, not domain models.
-
Handle HTTP concerns: routing, versioning, error responses.
[ApiController] [Route("api/[controller]")] public class OrdersController : ControllerBase { private readonly IMediator _mediator;
[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderCommand command)
{
var orderId = await _mediator.Send(command);
return CreatedAtAction(nameof(GetOrder), new { id = orderId }, orderId);
}
}
Quick Reference
When to Use Each DDD Concept
-
Entity: Object with unique identity (User, Order, Product).
-
Value Object: Immutable object defined by attributes (Money, Email, Address).
-
Aggregate: Cluster of objects with a root entity (Order with OrderLines).
-
Domain Service: Logic spanning multiple aggregates or too complex for one entity.
CQRS Guidelines
-
Commands: Use imperative verbs (CreateOrderCommand , CancelOrderCommand ).
-
Queries: Use nouns (GetOrderByIdQuery , ListOrdersQuery ).
-
DTOs: Never expose domain models directly to clients.
-
AsNoTracking: Always use for queries to optimize performance.
// Query example with AsNoTracking var orders = await _context.Orders .AsNoTracking() .Where(o => o.CustomerId == customerId) .Select(o => new OrderDto(o.OrderId, o.Status, o.Total)) .ToListAsync();
Infrastructure Composition
Organize DI configuration using extension methods in Infrastructure/Extensions .
Required Extension Methods
Create these in separate files under Infrastructure/Extensions :
-
AddPersistence - Database context and Unit of Work
-
AddRepositories - All repository implementations
-
AddServices - Domain and application services
-
AddExternalServices - HTTP clients, RabbitMQ, Redis, etc.
-
AddConfigurations - Configuration objects as singletons
-
AddKeyVault (IConfigurationBuilder) - Azure Key Vault setup
Program.cs Example
var builder = WebApplication.CreateBuilder(args);
// Configure Key Vault builder.Configuration.AddAzureKeyVault(builder.Configuration);
// Add services via extension methods builder.Services.AddPersistence(builder.Configuration); builder.Services.AddRepositories(); builder.Services.AddServices(); builder.Services.AddExternalServices(builder.Configuration); builder.Services.AddConfigurations(builder.Configuration);
// Add MediatR builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
builder.Services.AddControllers();
var app = builder.Build(); app.MapControllers(); app.Run();
For complete extension method implementations with examples: → See references/infrastructure-composition.md
Best Practices
Domain Layer
-
Keep domain pure; no infrastructure dependencies.
-
Enforce invariants within aggregates.
-
Use value objects to make implicit concepts explicit.
-
Keep aggregates focused and reasonably sized.
Application Layer
-
Keep handlers thin; complex logic belongs in domain.
-
Use Unit of Work for transactional consistency.
-
Validate commands but enforce business rules in domain.
-
Map domain models to DTOs for external consumption.
Infrastructure Layer
-
Use extension methods for clean DI composition.
-
Prioritize environment variables over appsettings for secrets.
-
Validate required configuration at startup.
-
Use Polly for resilience (retries, circuit breakers).
CQRS
-
Separate command and query concerns strictly.
-
Optimize queries with projections and AsNoTracking.
-
Return identifiers from commands, not full objects.
-
Use DTOs as contracts between layers.