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.

⸻

1. 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.

⸻

2. 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.

⸻

3. 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

⸻

4. Core Architectural Principle

Writes protect invariants. Reads optimize information access.

Therefore:
	•	Commands enforce business rules
	•	Queries are allowed to be pragmatic and denormalized

⸻

5. Placement in Clean Architecture

CQRS Light does not introduce new layers.
It reorganizes existing ones.

core/
└── <context>/
    └── application/
        ├── commands/   # Write side (Use Cases)
        └── queries/    # Read side (Query Use Cases)

Domain remains unchanged.

⸻

6. 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


⸻

7. 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.

⸻

8. 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.

⸻

9. 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

⸻

10. 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.

⸻

11. 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

⸻

12. Migration Path

CQRS Light allows incremental adoption:
	1.	Start with classic Clean Architecture
	2.	Separate commands and queries logically
	3.	Optimize read paths as needed
	4.	Introduce events or projections later (optional)

No rewrites required.

⸻

13. 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

⸻

14. Mental Model

Think of CQRS Light as:

One core truth, two access patterns.

The domain defines truth.
Queries define convenience.

⸻

15. 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.