Skip to content

Patterns

← index

Patterns are conditional. The files in the parent directory are principles — they apply to every decision. These apply only when the problem has the shape they solve.

Adopting a pattern the problem does not call for is itself a violation of ../principles/kiss.md and ../principles/yagni.md. Each file below has a "when not to use this" section. Read it before reaching for the pattern.

Core architecture

Pattern Use it when
Domain-Driven Design Business rules are complex and contested. Not for CRUD.
Hexagonal Architecture There is real logic and more than one external dependency.
Repository There is a domain model to keep free of persistence.

Code-level

Pattern Use it when
Functional Core, Imperative Shell Almost always. Decisions pure, effects at the edges.
Explicit Errors Almost always. Expected failures in the type signature.
Illegal States Unrepresentable Almost always. Let the type system carry the invariant.

Testing

Pattern Use it when
Testing Strategy Always. Many fast logic tests, few slow wiring tests.
Test Doubles Choosing a stand-in. Prefer fakes; mock only interactions.
Contract Tests Any fake exists, or services depend on each other.
Screenplay Writing end-to-end tests. Default over Page Objects.

Distributed / async

Pattern Use it when
Domain Events Side effects should attach without touching the use case.
CQRS Read and write needs genuinely diverge. Take the lowest level that works.
Outbox & Idempotency A state change must reliably reach another system.
Anti-Corruption Layer Integrating anything you do not control.

How these fit together

The core architecture patterns reinforce each other: hexagonal defines the ports, repository is the most common driven port, DDD supplies the model inside. Contract tests are what make the fakes at those ports trustworthy, and the functional core is what makes the inside of the hexagon fast to test.

The distributed group is the expensive one. Every pattern in it trades traceability and consistency for decoupling and resilience. Take them when a real requirement demands it, one level at a time — not because the architecture looks more serious with them.