Hexagonal Architecture (Ports and Adapters)
Hexagonal architecture, also called ports and adapters, puts your business logic at the center and pushes everything technical to the edges. The goal is simple: the core should not care whether data comes from a web request, a CLI, a database, or a test. This makes the heart of your system easy to test and change.
The shape
+----------------------------+
| Adapters (out) |
| DB Email Queue |
+------------|---------------+
[ Ports ]
+------------|---------------+
| DOMAIN |
| business rules only |
+------------|---------------+
[ Ports ]
+------------|---------------+
| Adapters (in) |
| HTTP CLI Tests |
+----------------------------+
Ports are interfaces
A port is a contract the domain defines. The domain says “I need to save an order” without saying how.
// Port: defined by the domain, no database details
class OrderRepository {
save(order) { throw new Error("not implemented"); }
}
Adapters implement ports
An adapter plugs a real technology into a port. You can swap it without touching the core.
// Adapter: implements the port using a real database
class SqlOrderRepository extends OrderRepository {
save(order) { /* SQL insert here */ }
}
The domain receives the port; it never imports the SQL code.
The flow
HTTP adapter -> input port -> DOMAIN -> output port -> DB adapter
I/O lives outside. Rules live inside. Dependencies always point inward, toward the domain.
Advantages
- Business logic is framework-free and easy to unit test.
- Swap databases, queues, or frameworks with minimal change.
- Clear separation between “what the system does” and “how it talks to the world”.
Disadvantages
- More interfaces and indirection, which feels heavy for tiny apps.
- A learning curve for teams used to direct database calls.
When to use it
Reach for hexagonal architecture when the business rules are complex and long-lived, when you want strong tests, or when you expect to change infrastructure. For a small script, it is overkill.