Application layer¶
The Application layer orchestrates business operations using the CQRS (Command Query Responsibility Segregation) pattern with MediatR. It sits between the API Layer and the Domain Layer, depending only on Domain.
Clean Architecture - use case orchestration
The Application layer contains no business rules and no infrastructure concerns. It coordinates work: receiving commands and queries, validating input, calling domain logic, and returning results.
Responsibility¶
- Commands: Write operations that change state (e.g.
CreateAnimalCommand) - Queries: Read operations that return data (e.g.
GetAnimalQuery,GetAllAnimalsQuery) - Handlers: Process commands and queries (e.g.
CreateAnimalCommandHandler) - Validators: FluentValidation validators for input validation (e.g.
CreateAnimalCommandValidator) - DTOs: Data transfer objects for external communication (e.g.
AnimalDto,WeightDto) - Mapping: AutoMapper profiles for entity-to-DTO transformations
- ValidationBehaviour: MediatR pipeline behaviour that validates all requests before handling
Components¶
graph LR
subgraph "Commands - write operations"
CMD1[CreateAnimalCommand<br>+ Handler<br>+ Validator]
CMD2[CreateAccountCommand<br>+ Handler<br>+ Validator]
end
subgraph "Queries - read operations"
Q1[GetAnimalQuery<br>+ Handler]
Q2[GetAllAnimalsQuery<br>+ Handler]
Q3[GetAccountQuery<br>+ Handler]
Q4[GetUserQuery<br>+ Handler]
end
subgraph "Cross-cutting"
VB[Validation Behaviour<br>MediatR Pipeline<br>Auto-validates before handlers]
MAP[AutoMapper Profile<br>Entity to DTO mapping]
end
subgraph "DTOs"
DTO1[AnimalDto]
DTO2[AccountDto]
DTO3[UserDto]
DTO4[WeightDto]
end
CMD1 --> VB
CMD2 --> VB
Q1 --> MAP
Q2 --> MAP
MAP --> DTO1
MAP --> DTO2
MAP --> DTO3
Commands and queries¶
Commands and queries are strictly separated. Commands modify state; queries only read. Both are routed through the MediatR pipeline, which runs validation automatically before the handler executes.
Validation pipeline¶
PetFolio uses FluentValidation with MediatR Pipeline Behaviours to automatically validate all commands and queries before they reach their handlers. This ensures consistent validation across the entire application without manual checks in controllers or handlers.
How validation is configured¶
Location: Petfolio.Service.Application/DependencyInjection.cs
AddValidatorsFromAssembly- automatically discovers and registers allAbstractValidator<T>classesAddOpenBehavior(typeof(ValidationBehaviour<,>))- adds validation to the MediatR pipeline- Validators are registered as scoped services and automatically injected into
ValidationBehaviour
How validation works¶
Creating validators¶
Location: Petfolio.Service.Application/{Feature}/Validators/
Rules for validators:
- Use
internal sealed class(they are implementation details) - Inherit from
AbstractValidator<TCommand>orAbstractValidator<TQuery> - Define all validation rules in the constructor
- No manual registration needed - auto-discovered by
AddValidatorsFromAssembly - Use centralised validation constants from
Validationclass in Domain layer - Validators automatically execute before the command handler runs
Validation error handling¶
When validation fails, ValidationBehaviour throws ValidationException with all collected errors. Controllers handle this via the Result pattern.
Benefits of this approach:
- Zero boilerplate: No
if (!ModelState.IsValid)checks in controllers - Consistent validation: All commands validated the same way
- Single responsibility: Controllers only handle HTTP, validators only validate
- Easy testing: Test validators independently, no need to mock validation logic
- Fail fast: Invalid requests never reach handlers
- Centralised error handling: All validation errors collected and returned together