software-engineering-canon

A curated canon of battle-tested software design principles, type-driven techniques and architectures.

Architectures

Two independent axes: the macro structure of the system (modules and their boundaries) and the micro structure inside a module. Comparing them as rivals is a category error.

Hexagonal architecture (ports and adapters)

The application core talks to the outside world only through ports, which are purposeful conversations; adapters plug technologies into ports. The application can be driven equally by users, programs, tests or batch jobs, and developed and tested in isolation from databases and UIs. The hexagon is only a drawing convention: it leaves room to add as many ports and adapters as needed, where a layered drawing forces a one-dimensional picture.

Best when
The default choice for most projects: infrastructure-independent, swappable components, and in-memory adapters that make tests trivial.
Origin
Alistair Cockburn, 2005
Served by
SOLID (Dependency inversion is the mechanism.)
Same family as
Clean and onion architecture
Pairs with
Functional core, imperative shell, Modular monolith (Macro structure (modules) and micro structure (inside a module) are independent axes.), Test at the seams (In-memory adapters make core tests fast.)
Supplied by
Domain-driven design (The domain model lives inside the hexagon.)
Enforced by
Evolutionary architecture with fitness functions
Sources
Hexagonal architecture, Hexagonal architecture pattern

Clean and onion architecture

The same dependency rule as hexagonal, expressed as concentric layers that point inward.

Best when
The same family as hexagonal; pick the vocabulary your team already has.
Origin
Robert C. Martin and Jeffrey Palermo
Same family as
Hexagonal architecture (ports and adapters)
Served by
SOLID (Dependency inversion is the mechanism.)

Functional core, imperative shell

Pure decision logic in the core; I/O and effects in a thin shell.

Best when
Pairs naturally with hexagonal and with type-driven design.
Pairs with
Hexagonal architecture (ports and adapters)
Sources
Building modern architectures: Functional Core, Imperative Shell

Domain-driven design

Model the business language: bounded contexts, aggregates, value objects and a ubiquitous language.

Best when
Complex domains; it supplies the inside of the hexagon.
Origin
Eric Evans, 2003
Supplies
Hexagonal architecture (ports and adapters) (The domain model lives inside the hexagon.)

Vertical slices

Organise code by feature or use case rather than by technical layer.

Best when
Change locality; CRUD-heavy or API-driven applications.
Pairs with
Modular monolith (Slices organise the inside of a module.)
Sources
Modules vs vertical slices: macro vs micro architecture

Modular monolith

Real modules with enforced boundaries, deployed as one unit.

Best when
Most teams: it avoids the operating cost of microservices and keeps the option to extract services later.
Pairs with
Hexagonal architecture (ports and adapters) (Macro structure (modules) and micro structure (inside a module) are independent axes.), Vertical slices (Slices organise the inside of a module.)
Enforced by
Evolutionary architecture with fitness functions
Sources
Modules vs vertical slices: macro vs micro architecture

CQRS and event sourcing

Separate read and write models; keep state as a log of events.

Best when
Only where audit, temporal or scale needs justify the cost.

Evolutionary architecture with fitness functions

Automated checks, such as dependency-rule tests and complexity or coupling budgets, keep the architecture honest.

Best when
Always: boundaries without enforcement rot into a big ball of mud.
Enforces
Hexagonal architecture (ports and adapters), Modular monolith
Served by
Enforce boundaries with tooling