CQRS Implementation

This template separates read and write operations for optimal performance and scalability.

Write side: EF Core with change tracking, IRepository, Unit of Work, ACID transactions, RowVersion optimistic concurrency.

Read side: Dapper for fast SQL queries. Flexible data sources — read replicas, Redis, Elasticsearch. Eventually consistent.

Architecture tests enforce separation: query handlers cannot use IRepository; command handlers cannot use IReadRepository.

Dual Event Handlers

IDomainEventHandler (Transactional)

Executes within the same transaction. Modifies aggregate state via repositories. Failure causes rollback. Used for cross-aggregate coordination requiring ACID guarantees.

IIntegrationEventHandler (Asynchronous)

Executes via outbox pattern outside the command scope. Used for notifications, message bus publishing, logging, and analytics. Failures do not rollback the command.

See ADR-003 for the full decision rationale.

Outbox Pattern

Integration events are written to an outbox table within the command's database transaction. A background processor polls, deserializes, and invokes handlers with automatic retry.

The IOutboxStore and IOutboxPublisher abstractions allow swapping from in-process delivery to a message bus (RabbitMQ, Azure Service Bus) without changing business logic.

See ADR-004 for details.

Cross-Aggregate Coordination

When operations span multiple aggregates (e.g., completing a work order updates Asset and Technician state), domain event handlers coordinate changes within a single transaction.

See ADR-001.

Optimistic Concurrency

SQL Server ROWVERSION columns detect concurrent modifications. Conflicts return structured errors for the client to retry.

See ADR-002.

Architecture Tests

Automated tests in CleanArchitecture.Core.ArchitectureTests enforce:

All Architectural Decision Records