Skip to content

ADR-008 CQRS & Mediator Strategy

Status: Accepted Date: 2026-06-19


Context

The platform uses CQRS and Mediator as a core application pattern inside domain-heavy services.

We are building a custom mediator and CQRS framework under the platform/mediator and platform/cqrs modules.

We must define:

  • How commands, queries, and events are structured
  • How handlers are executed
  • How pipeline behaviors work
  • How transactions are managed
  • How validation, logging, and security are applied consistently
  • How async and sync flows integrate with event-driven architecture

We must avoid coupling application logic to Spring internals.


Decision

We will implement a custom Mediator-based CQRS system with pluggable pipeline behaviors.


1. Core CQRS Model

Command

Represents a state-changing operation.

```text id="a1c2qz" Command → CommandHandler → Result

Rules:

* MUST NOT return domain entities directly
* MUST be immutable
* MUST represent intent (not data access)

---

## Query

Represents a read-only operation.

```text id="b2d9lm"
Query → QueryHandler → DTO

Rules:

  • MUST NOT modify state
  • MAY bypass domain layer for performance (read models allowed)
  • SHOULD return optimized DTOs

Event

Represents something that already happened.

```text id="c3k8qp" Event → EventHandler

Rules:

* MUST be immutable
* MUST be versioned (`*.v1`)
* MUST be publishable to Kafka via Outbox

---

# 2. Mediator Execution Model

All requests go through a single entry point:

```text id="m1n9qk"
Mediator.send(request)

Flow:

```text id="m2p8lm" Request → Pipeline Behaviors → Handler Resolution → Handler Execution → Response

---

# 3. Pipeline Behaviors (Cross-Cutting Concerns)

All requests pass through ordered pipeline behaviors:

### Mandatory behaviors:

1. ValidationBehavior
2. SecurityBehavior
3. LoggingBehavior
4. TransactionBehavior (for commands)
5. PerformanceBehavior

---

Example flow:

```text id="p9x1lm"
Request
 → Validation
 → Authorization
 → Logging
 → Transaction
 → Handler


4. Transaction Rules

  • Commands MUST run inside transactional behavior
  • Queries MUST NOT start transactions
  • Event publishing MUST NOT block transaction commit

5. Handler Rules

  • One handler per command/query/event
  • Handlers MUST be stateless
  • Handlers MUST NOT call other handlers directly
  • Cross-handling MUST be done via domain services or events

6. Integration with Domain Layer

  • Handlers orchestrate use cases
  • Domain contains business rules
  • Application layer contains workflow logic

7. Async Event Integration

  • Domain events are converted into integration events
  • Integration events are published via Outbox pattern
  • Handlers MUST NOT publish directly to Kafka

8. Spring Integration Rule

  • Spring is used ONLY for wiring (dependency injection)
  • Business logic MUST NOT depend on Spring annotations

Consequences

Positive

  • Highly structured application flow
  • Fully testable request pipeline
  • Clear separation of concerns
  • AI-friendly execution model
  • Easy to extend with new behaviors

Negative

  • Requires disciplined handler design
  • Slight overhead for simple operations
  • Learning curve for new developers

Alternatives Considered

Direct service calls (no mediator)

Rejected due to lack of structure and cross-cutting control.

Using third-party CQRS libraries

Rejected to maintain full control and customization.


  • ADR-004 Architecture Style
  • ADR-007 Platform Library Strategy
  • ADR-009 Event Driven Architecture