4.8 KiB
CQRS Light with Clean Architecture
This document defines CQRS Light as a pragmatic, production-ready approach that integrates cleanly with Clean Architecture.
It is intentionally non-dogmatic, avoids event-sourcing overhead, and focuses on clarity, performance, and maintainability.
⸻
- What CQRS Light Is
CQRS Light separates how the system writes data from how it reads data — without changing the core architecture.
Key properties: • Commands and Queries are separated logically, not infrastructurally • Same database is allowed • No event bus required • No eventual consistency by default
CQRS Light is an optimization, not a foundation.
⸻
- What CQRS Light Is NOT
CQRS Light explicitly does not include: • Event Sourcing • Message brokers • Projections as a hard requirement • Separate databases • Microservices
Those can be added later if needed.
⸻
- Why CQRS Light Exists
Without CQRS: • Reads are forced through domain aggregates • Aggregates grow unnaturally large • Reporting logic pollutes the domain • Performance degrades due to object loading
CQRS Light solves this by allowing: • Strict domain logic on writes • Flexible, optimized reads
⸻
- Core Architectural Principle
Writes protect invariants. Reads optimize information access.
Therefore: • Commands enforce business rules • Queries are allowed to be pragmatic and denormalized
⸻
- Placement in Clean Architecture
CQRS Light does not introduce new layers. It reorganizes existing ones.
core/ └── / └── application/ ├── commands/ # Write side (Use Cases) └── queries/ # Read side (Query Use Cases)
Domain remains unchanged.
⸻
- Command Side (Write Model)
Purpose • Modify state • Enforce invariants • Emit outcomes
Characteristics • Uses Domain Entities and Value Objects • Uses Repositories • Uses Output Ports • Transactional
Example Structure
core/racing/application/commands/ ├── CreateLeagueUseCase.ts ├── ApplyPenaltyUseCase.ts └── RegisterForRaceUseCase.ts
⸻
- Query Side (Read Model)
Purpose • Read state • Aggregate data • Serve UI efficiently
Characteristics • No domain entities • No invariants • No side effects • May use SQL/ORM directly
Example Structure
core/racing/application/queries/ ├── GetLeagueStandingsQuery.ts ├── GetDashboardOverviewQuery.ts └── GetDriverStatsQuery.ts
Queries are still Use Cases, just read-only ones.
⸻
- Repositories in CQRS Light
Write Repositories • Used by command use cases • Work with domain entities • Enforce consistency
core/racing/domain/repositories/ └── LeagueRepositoryPort.ts
⸻
Read Repositories • Used by query use cases • Return flat, optimized data • Not domain repositories
core/racing/application/ports/ └── LeagueStandingsReadPort.ts
Implementation lives in adapters.
⸻
- Performance Benefits (Why It Matters)
Without CQRS Light • Aggregate loading • N+1 queries • Heavy object graphs • CPU and memory overhead
With CQRS Light • Single optimized queries • Minimal data transfer • Database does aggregation • Lower memory footprint
This results in: • Faster endpoints • Simpler code • Easier scaling
⸻
- Testing Strategy
Commands • Unit tests • Mock repositories and ports • Verify output port calls
Queries • Simple unit tests • Input → Output verification • No mocks beyond data source
CQRS Light reduces the need for complex integration tests.
⸻
- When CQRS Light Is a Good Fit
Use CQRS Light when: • Read complexity is high • Write logic must stay strict • Dashboards or analytics exist • Multiple clients consume the system
Avoid CQRS Light when: • Application is CRUD-only • Data volume is small • Read/write patterns are identical
⸻
- Adoption Rule (Strict)
CQRS Light is a structural rule inside Core.
If CQRS Light is used:
- commands and queries MUST be separated by responsibility
- queries MUST remain read-only and must not enforce invariants
This document does not define a migration plan.
⸻
- Key Rules (Non-Negotiable) • Commands MUST use the domain • Queries MUST NOT modify state • Queries MUST NOT enforce invariants • Domain MUST NOT depend on queries • Core remains framework-agnostic
⸻
- Mental Model
Think of CQRS Light as:
One core truth, two access patterns.
The domain defines truth. Queries define convenience.
⸻
- Final Summary
CQRS Light provides: • Cleaner domain models • Faster reads • Reduced complexity • Future scalability
Without: • Infrastructure overhead • Event sourcing complexity • Premature optimization
It is the safest way to gain CQRS benefits while staying true to Clean Architecture.