| name | cqrs-mediatr-guidelines |
| description | Apply project CQRS conventions for commands, queries, handlers, validators, caching, and specification-driven reads. |
| type | pattern |
| enforcement | suggest |
| priority | high |
Purpose
Use this skill when shaping Application-layer requests, handlers, validators, and response contracts. It keeps MediatR flows predictable and consistent with repository, caching, and DTO-mapping rules.
When to Load
- Keywords: command, query, handler, MediatR, CQRS, validator, cache invalidation, specification.
- File patterns:
**/*Command.cs, **/*Query.cs, **/*Handler.cs, **/*Validator.cs, Explore.Application/**/*.cs.
- Intent IDs:
add-cqrs-handler, add-get-endpoint, add-write-endpoint, update-repository-query.
When NOT to Load
Must-Read Docs
Top 5 Invariants
- Commands return
BaseCommandResponse<Guid> for create or update work or bool for many deletes, while queries return DTOs or PaginatedResult<TDto>.
- Repositories return entities and handler code performs DTO mapping, so projection boundaries stay in Application.
- Validators are manually instantiated inside handlers and are never injected as
IValidator<T> through DI.
- Handlers pass
CancellationToken end to end and use HybridCache with GetOrCreateAsync for queries and RemoveAsync for command invalidation.
IQuerySpecification<T> and immutable fluent composition via And(...) live in Application, while repositories apply the specification to IQueryable<T>.
Top 5 Anti-Patterns
- Mixing command and query logic in one handler creates blurred responsibilities and makes pipeline behavior harder to reason about.
- Injecting
IValidator<T> through DI breaks the project validation standard and weakens handler-local control.
- Returning domain entities from handlers leaks persistence-shaped objects into API or Blazor consumers.
- Using
ExploreDbContext directly in handlers bypasses repository abstractions and couples Application to EF Core.
- Returning
IQueryable from repositories leaks EF concerns into Application and lets handlers compose persistence logic ad hoc.
Minimal Examples
public sealed record GetEventByIdQuery(Guid Id);
public sealed class GetEventByIdHandler(IEventRepository repository, HybridCache cache)
{
public async Task<EventDto?> Handle(GetEventByIdQuery request, CancellationToken cancellationToken)
{
var validator = new GetEventByIdQueryValidator();
await validator.ValidateAndThrowAsync(request, cancellationToken);
return await cache.GetOrCreateAsync(
$"events:{request.Id}",
async token =>
{
Event? entity = await repository.GetByIdAsync(request.Id, token);
return entity is null ? null : new EventDto(entity.Id, entity.Title);
},
cancellationToken: cancellationToken);
}
}
IQuerySpecification<Event> spec = EventQuerySpecification.Create()
.WithPublishedOnly()
.And(EventQuerySpecification.Create().WithTenant(tenantId))
.And(EventQuerySpecification.Create().WithSearch(searchTerm));
Verification Hooks
dotnet test --project Event.Architecture.Tests/Event.Architecture.Tests.csproj --configuration Release --verbosity quiet --filter FullyQualifiedName~Event.Architecture.Tests.CqrsPatternTests
dotnet test --project Event.Application.UnitTests/Event.Application.UnitTests.csproj --configuration Release --verbosity quiet
dotnet build --configuration Release --verbosity quiet
Related Skills