CQRS Implementation
Relevant source files
The following files were used as context for generating this wiki page:
- BuildingBlocks/BuildingBlocks.Application/Cqrs/Command.cs
- BuildingBlocks/BuildingBlocks.Application/Cqrs/Query.cs
- BuildingBlocks/BuildingBlocks.Application/Cqrs/OrchestrationCommand.cs
- BuildingBlocks/BuildingBlocks.Application/Behaviors/TransactionBehavior.cs
- BuildingBlocks/BuildingBlocks.Tests/TransactionBehaviorTests.cs
- Core/Scheduling/Scheduling.Application/Patients/Commands/CreatePatientCommand.cs
- claude.md
Introduction
The Command Query Responsibility Segregation (CQRS) pattern is a core architectural pillar of this project, designed to separate the "write" side (commands) from the "read" side (queries) of the system. This separation allows for optimized data models, independent scaling, and clearer business logic boundaries. Within this project, CQRS is implemented using the MediatR library to facilitate decoupled communication between the application's API endpoints and their corresponding handlers.
The implementation ensures that commands, which represent intentions to change state, are distinct from queries, which are read-only operations. This distinction is further reinforced by pipeline behaviors that automatically manage database transactions for commands while bypassing them for queries to improve performance.
Sources: claude.md:28-34, BuildingBlocks/BuildingBlocks.Application/Cqrs/Command.cs, BuildingBlocks/BuildingBlocks.Application/Cqrs/Query.cs
Architecture and Components
The CQRS infrastructure is built upon base records and pipeline behaviors that define how requests are processed through the system.
Core Request Types
| Component | Description | Transactional |
|---|---|---|
Command<TResponse> | Represents an action that changes system state. | Yes (Default) |
Query<TResponse> | Represents a read-only operation. | No |
OrchestrationCommand<TResponse> | Coordinates multiple child commands; does not have its own transaction. | No (Child-only) |
Sources: BuildingBlocks/BuildingBlocks.Application/Cqrs/Command.cs:8-12, BuildingBlocks/BuildingBlocks.Application/Cqrs/Query.cs:8-11, BuildingBlocks/BuildingBlocks.Application/Cqrs/OrchestrationCommand.cs:17-23
Request Flow and Transaction Management
Requests follow a specific path where they are intercepted by MediatR pipeline behaviors before reaching their final handlers. The TransactionBehavior is a critical component that manages the lifecycle of database transactions.
The diagram shows the decision logic within the TransactionBehavior to determine if a database transaction should be initiated based on the request type and properties.
Sources: BuildingBlocks/BuildingBlocks.Application/Behaviors/TransactionBehavior.cs:23-53, BuildingBlocks/BuildingBlocks.Tests/TransactionBehaviorTests.cs:33-65
Command Implementation
Commands are defined as records inheriting from the base Command<TResponse> type. They encapsulate all data required to perform a state-changing operation.
Transactional Logic
By default, all commands are wrapped in a database transaction managed by the IUnitOfWork. If a ValidationException occurs, the transaction is still committed to preserve potential side effects from validators (such as audit logs), whereas any other Exception triggers a rollback.
public abstract record Command<TResponse> : IRequest<TResponse>
{
[JsonIgnore]
public bool SkipTransaction { get; init; }
}
Sources: BuildingBlocks/BuildingBlocks.Application/Cqrs/Command.cs:11-17, BuildingBlocks/BuildingBlocks.Application/Behaviors/TransactionBehavior.cs:40-52
Orchestration Commands
Orchestration commands are used for long-running workflows or saga patterns. Unlike standard commands, they set SkipTransaction to true. This ensures the orchestrator doesn't hold a single long-lived transaction, but rather allows each child command it dispatches via IMediator.Send() to manage its own atomic transaction boundary.
Sources: BuildingBlocks/BuildingBlocks.Application/Cqrs/OrchestrationCommand.cs:5-23
Query Implementation
Queries are read-only operations that inherit from Query<TResponse>. The system architecture strictly enforces that queries should not be wrapped in transactions to minimize overhead and prevent accidental state changes during read operations.
This sequence illustrates that queries bypass the transaction logic within the pipeline.
Sources: BuildingBlocks/BuildingBlocks.Application/Cqrs/Query.cs:8-11, BuildingBlocks/BuildingBlocks.Application/Behaviors/TransactionBehavior.cs:64-74, BuildingBlocks/BuildingBlocks.Tests/TransactionBehaviorTests.cs:134-147
Validation and Security
CQRS requests are integrated with FluentValidation and role-based security.
- Command Validation: Commands often include internal validator classes (e.g.,
CreatePatientCommandValidator) that useUserValidator<T>to enforce both data integrity and user permissions. - Role Authorization: Validators can restrict execution to specific roles like
Admin,Doctor, orNurse. - Error Handling: Validation failures typically return an
ErrorCode.Forbiddenor standard validation messages, which are handled before the command logic executes.
Sources: Core/Scheduling/Scheduling.Application/Patients/Commands/CreatePatientCommand.cs:30-40, Core/Scheduling/Scheduling.Application/Patients/Commands/ActivatePatientCommand.cs:22-30
Conclusion
The CQRS implementation provides a structured and safe environment for developing complex domain logic. By enforcing transaction boundaries at the command level and allowing lightweight query execution, the system maintains high data integrity while ensuring performance. The inclusion of OrchestrationCommand further allows the system to handle complex distributed workflows without compromising the atomicity of individual operations.