There are two kinds of complexity in a system:
- Business complexity: the rules that give the software its value.
- Technical complexity: frameworks, databases, queues, protocols.
Hexagonal architecture, proposed by Alistair Cockburn in 2005 under the name Ports and Adapters, separates the two. Frameworks and dependencies can then be replaced or upgraded without interfering with the business rules.
- Domain: the business rules. It imports nothing from frameworks, databases or HTTP.
- Ports: interfaces defined by the domain. Input ports say what the system does (use cases); output ports say what it needs from the world (save an order, send an email).
- Adapters: the technical implementations that plug into the ports: a REST controller, a Postgres repository, an email client.
The rule that holds it all together: dependencies point inward. The domain doesn’t know the adapters; the adapters know the domain.
// output port, defined by the domaintype OrderRepository interface { Save(order Order) error}
// use case: only knows the portfunc CreateOrder(repo OrderRepository, items []Item) (Order, error) { order := NewOrder(items) // business rules return order, repo.Save(order)}In production, repo is the Postgres adapter; in tests, an in-memory version, without starting any database.
Why it pays off
- Sustainable growth: the software stays easy to change over time.
- Software has to pay for itself over time, and swapping a technology shouldn’t require rewriting the business.
- The software should be designed by you, not by the framework.
- The pieces need to fit together and, eventually, be replaced.
The cost is more structure (interfaces and mappings) than a simple CRUD needs. It is worth it when there are real business rules.
See also: Software architecture fundamentals.
- To dig deeper: Hexagonal architecture, by Alistair Cockburn