2.4 KiB
2.4 KiB
Application Layer (CQRS + MediatR)
Responsibility
Command/query handlers orchestrating domain logic via MediatR pipeline. Sits between API controllers and Domain layer.
Dependencies
- MediatR: Command/query dispatch
- FluentValidation: Request validation via pipeline behavior
- AutoMapper: Domain ↔ DTO mapping
Consumers
- API Controllers: Send commands/queries via
IMediator - Integration tests: Direct handler invocation
Module Structure
Application/
├── Common/
│ ├── Behaviors/ # MediatR pipeline (validation, logging)
│ └── Mappings/ # AutoMapper profiles
├── Features/ # One folder per aggregate
│ └── Orders/
│ ├── Commands/ # CreateOrder/, UpdateOrder/ (handler + validator + DTO)
│ └── Queries/ # GetOrder/, ListOrders/
└── DependencyInjection.cs # Service registration
Handler Pattern (Command with Validation)
public record CreateOrderCommand(string CustomerId, List<LineItemDto> Items)
: IRequest<Result<OrderDto>>;
public class CreateOrderValidator : AbstractValidator<CreateOrderCommand> {
public CreateOrderValidator(IOrderRepository repo) {
RuleFor(x => x.CustomerId).NotEmpty();
RuleFor(x => x.Items).NotEmpty();
}
}
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, Result<OrderDto>> {
public async Task<Result<OrderDto>> Handle(
CreateOrderCommand request, CancellationToken ct) {
var order = Order.Create(request.CustomerId, request.Items); // Domain factory
await _repo.AddAsync(order, ct);
await _unitOfWork.SaveChangesAsync(ct);
return Result.Ok(_mapper.Map<OrderDto>(order));
}
}
Architectural Boundaries
- NO domain logic in handlers: Handlers orchestrate, domain objects contain logic
- NO direct DbContext access: Use repository abstractions
- NO cross-feature references: Features are independent vertical slices