Hexagonal and Clean Architecture
By the end of this module you will be able to:
- Explain the Dependency Inversion Principle and how it enables technology independence
- Describe hexagonal architecture using ports and adapters terminology correctly
- Map Clean Architecture's four concentric rings to a real system
- Apply these patterns to make business logic independently testable without a database

Real-world case · 2015 to 2019
Monzo built a cloud-native UK bank from scratch. Their domain logic had zero database dependencies.
When Monzo launched in 2015, its engineering team made a deliberate choice: the core banking engine - account balances, transactions, credit limits - would be written in Kotlin and Java with no database dependencies. Business rules were plain objects. The repository interface was defined in the domain layer. PostgreSQL was an adapter implementing that interface, not a dependency of the domain.
The practical consequence was testability. A unit test for the funds transfer rule passed an in-memory repository to the use case. That test ran in 8 milliseconds. The same use case, connected to PostgreSQL in production, ran in 40 milliseconds including the network round-trip. Monzo's CI pipeline ran 4,200 unit tests in under 60 seconds - impossible with database-coupled code.
In 2018 Monzo migrated their notification adapter from Twilio to their own notification service. Zero domain code changed. The notification port interface remained identical; only the adapter implementation was replaced. This is hexagonal architecture working exactly as Alistair Cockburn intended when he described ports and adapters in 2005.
What is a bank account without a database? According to hexagonal architecture, it is a domain object - and the database is just a detail.
With the learning outcomes established, this module begins by examining hexagonal architecture: alistair cockburn, 2005 in depth.
16.1 Hexagonal architecture: Alistair Cockburn, 2005
Alistair Cockburn introduced hexagonal architecture in 2005 under the subtitle "Ports and Adapters." The visual metaphor is a hexagon representing the application core, with ports on every face and adapters plugging into those ports from outside. The hexagon has no special meaning; Cockburn used it to signal that no side is privileged - there is no "top" (presentation) and no "bottom" (database).
The pattern divides the world into two sides. The left side contains driving adapters: components that initiate interaction with the application. An HTTP REST controller is a driving adapter. A command-line tool is a driving adapter. A test suite is a driving adapter. All of them call the application through the same driving port interface. The right side contains driven adapters: components the application calls out to. A PostgreSQL repository is a driven adapter. A Kafka publisher is a driven adapter. A Twilio SMS sender is a driven adapter.
A port is an interface, not a framework. Driving ports are interfaces the application exposes for callers. Driven ports are interfaces the application defines for infrastructure. The critical point is ownership: driven ports are defined in the application layer, not in the infrastructure layer. The application says "I need something that can save an account" and definesAccountRepository. PostgreSQL implements it in an adapter.
“The application is blissfully ignorant of the nature of the input device. The test cases also provide a driving adapter for the application. When the application wants to send output, it sends it out a port to a handler.”
Cockburn, A. (2005) - Hexagonal Architecture (Ports and Adapters), alistair.cockburn.us
Cockburn's formulation makes the test harness architecturally equivalent to the HTTP adapter. Both are driving adapters calling the same port. This is why test-driven development is natural in hexagonal architecture: tests are not a special case; they are just another caller of the application's driving port.
With an understanding of hexagonal architecture: alistair cockburn, 2005 in place, the discussion can now turn to clean architecture: robert martin, 2012, which builds directly on these foundations.
16.2 Clean architecture: Robert Martin, 2012
Robert C. Martin published Clean Architecture in 2017, synthesising hexagonal architecture (Cockburn, 2005), Onion Architecture (Jeffrey Palermo, 2008), and Screaming Architecture into a unified model with four concentric rings and one overriding rule: the Dependency Rule.
The four rings, from innermost to outermost, are: Enterprise Business Rules (entities - pure domain objects), Application Business Rules (use cases -- application-specific orchestration), Interface Adapters (controllers, presenters, gateways), and Frameworks and Drivers (HTTP frameworks, databases, message brokers, UI frameworks). The innermost ring is the most stable; the outermost is the most volatile.
The Dependency Rule is absolute: source code dependencies can only point inward. Nothing in an inner ring imports from an outer ring. An entity never imports a controller. A use case never imports a database library. An interface adapter imports use cases, but use cases do not import adapters. This means the domain model and use cases can be compiled and tested without any framework present.
“The overriding rule that makes this architecture work is the Dependency Rule. This rule says that source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle.”
Martin, R.C. (2017) - Part III, Design Principles
This single rule eliminates an entire class of coupling problems. If the domain model never imports a framework class, it can be tested without the framework. If the use case never imports a database class, business logic can be verified without a database. The inward dependency rule makes every inner ring independently testable and framework-agnostic.
Common misconception
“Hexagonal architecture is just layered architecture renamed.”
In traditional layered architecture, the data access layer is at the bottom and business logic imports it directly - the dependency points downward toward infrastructure. In hexagonal architecture, the domain is at the centre and ALL infrastructure is an adapter implementing an interface the domain owns. The dependency is inverted: infrastructure depends on the domain interface, not the other way around. The structural consequence is that you can replace the database without touching business logic.
With an understanding of clean architecture: robert martin, 2012 in place, the discussion can now turn to the dependency rule explained, which builds directly on these foundations.
16.3 The dependency rule explained
Following the dependency rule through a concrete example clarifies how each ring behaves. Consider a funds transfer use case in the Monzo banking domain. TheAccount entity in the innermost ring holds the balance and enforces the invariant that an account cannot go below its overdraft limit. It imports nothing except the Java standard library.
The TransferFundsUseCase in the Application Business Rules ring orchestrates the transfer: it calls AccountRepository (a driven port interface it owns) to load both accounts, calls Account.debit()and Account.credit(), then calls AccountRepository.save(). The use case imports the entity and the port interface. It imports no database library, no HTTP library, no framework annotation.
The JdbcAccountRepository in the Interface Adapters ring implementsAccountRepository using Spring JDBC. It imports Spring, JDBC, and the domain interface. The dependency arrow points inward: the adapter depends on the port interface, the port interface does not depend on the adapter. If Monzo decides to replace JDBC with jOOQ, only the adapter changes. The use case and entity are untouched.
With an understanding of the dependency rule explained in place, the discussion can now turn to ports: driving versus driven, which builds directly on these foundations.
16.4 Ports: driving versus driven
Understanding port direction is the most important concept in hexagonal architecture. Driving ports (also called primary or inbound) are interfaces the application exposes for external callers to invoke. The application defines what it offers; callers adapt to that contract. A TransferFundsPort is a driving port. An HTTP controller, a CLI command, and a test case all call it.
Driven ports (also called secondary or outbound) are interfaces the application defines for infrastructure it needs. The application says what it requires from the outside world. AccountRepository, FraudChecker, and NotificationSender are driven ports. PostgreSQL, an external fraud API, and Twilio are driven adapters implementing those ports. The application owns the interface contract; infrastructure conforms to it.
This ownership distinction is what makes driven ports different from ordinary dependency injection. In a traditional layered application, the business logic imports PostgresAccountRepository from the data access layer. The data access layer owns the interface. In hexagonal architecture, the application defines AccountRepository in its own package. The data access layer implements it. The arrow of ownership is reversed.
With an understanding of ports: driving versus driven in place, the discussion can now turn to adapters: primary and secondary, which builds directly on these foundations.

16.5 Adapters: primary and secondary
Primary adapters translate external input into domain calls. An HTTP REST controller receives a JSON payload, validates it, translates it into aTransferFundsCommand value object, and calls the driving port. A CLI adapter parses command-line arguments and calls the same port. A gRPC adapter does the same over a binary protocol. All three are interchangeable because they all call through the same port interface.
Secondary adapters implement driven ports using specific technologies. AJdbcAccountRepository implements AccountRepositoryusing SQL. A RedisAccountCache implements a read variant of the same port. A KafkaEventPublisher implementsDomainEventPublisher by writing to a Kafka topic. Swapping a secondary adapter requires only that the new implementation satisfies the port interface contract.
The test doubles pattern exploits secondary adapters directly. AnInMemoryAccountRepository implements AccountRepositorywith a plain HashMap. It satisfies the interface contract and can be used in place of the PostgreSQL adapter in unit tests. This is not mocking; it is a genuine secondary adapter that happens to store data in memory rather than a database. Monzo's CI pipeline uses in-memory adapters exclusively for unit tests, reserving integration tests for production adapters.
Common misconception
“Clean architecture adds too much complexity for small projects.”
The core abstraction - domain logic with no infrastructure dependencies - applies even to small projects. A single interface separating the domain from the database is not complex; it is 10 lines of code. What changes with project size is how many ports and adapters you have. A small project may have one repository port and one use case. The pattern scales from there without changing the fundamental structure. The alternative - coupling domain logic to infrastructure from day one - makes the project harder to test immediately and harder to change at any size.
With an understanding of adapters: primary and secondary in place, the discussion can now turn to testing benefits: speed and isolation, which builds directly on these foundations.
16.6 Testing benefits: speed and isolation
The most immediate practical benefit of hexagonal architecture is test speed. When domain logic has no infrastructure dependencies, unit tests need no database container, no HTTP server, no external API sandbox. A test for a funds transfer rule that once required a running PostgreSQL instance now passes an in-memory repository and runs in 8 milliseconds. A test suite of 500 such tests runs in under 10 seconds versus 25 minutes with database-coupled code.
The second benefit is isolation. When a test fails because of a domain rule violation, there is no ambiguity about whether the failure is in the business logic or the database adapter. The in-memory adapter is simple enough to be visually verified. Domain logic bugs are not obscured by infrastructure noise.
The third benefit is changeability. When the Monzo team replaced Twilio with their own notification service in 2018, the change was confined entirely to the secondary adapter implementing NotificationSender. The use case code calling notificationSender.send() was unchanged. The test suite continued to pass against the same in-memory adapter. The migration took one sprint instead of the three-year ordeal in the fintech case study from the Applied modules.

A reporting service's business logic calls pd.read_sql(query, postgres_engine) directly to fetch data. Using hexagonal architecture, what is the correct refactoring?
Which ring of Clean Architecture contains the rule 'a bank account balance cannot go below the overdraft limit'?
A team's CI pipeline takes 8 minutes because every unit test requires a PostgreSQL Testcontainer. How does hexagonal architecture address this?
Key takeaways
- Hexagonal architecture (Cockburn, 2005) places the domain at the centre. Driving ports are interfaces the application exposes. Driven ports are interfaces the application defines for infrastructure. All adapters plug into ports from outside.
- Clean Architecture (Martin, 2017) uses four concentric rings with one rule: dependencies point inward only. The domain entity ring has no imports from outer rings. Frameworks and databases are in the outermost ring.
- The key difference from layered architecture is ownership: driven port interfaces are defined in the application layer, not the infrastructure layer. The arrow from application to infrastructure is replaced by an arrow from infrastructure to an interface the application owns.
- The primary practical benefit is testability. Driven adapters can be replaced with in-memory implementations, allowing unit tests to run in milliseconds without databases, network calls, or external services.
- Monzo's 2018 notification adapter replacement (Twilio to own service) with zero domain code changes is the canonical real-world example of hexagonal architecture's changeability benefit.
- Framework annotations in domain entities (@Entity, @JsonProperty) violate the dependency rule. Persistence and serialisation belong in adapters, not in the innermost ring.
Standards and sources cited in this module
Cockburn, A. (2005). Hexagonal Architecture. alistair.cockburn.us
Full article, original ports and adapters formulation
The primary source for hexagonal architecture. Sections 16.1 and 16.4 draw directly on Cockburn's definition of driving/driven ports and the equivalence of test adapters and production adapters.
Chapters 22 (The Clean Architecture) and 5 (Dependency Inversion Principle)
The source of Clean Architecture's four rings and the Dependency Rule quoted in Section 16.2. Martin unifies hexagonal architecture, Onion Architecture, and Screaming Architecture into a single model.
Martin, R.C. (1996). The Dependency Inversion Principle. C++ Report
Original DIP paper
The foundational reference predating both hexagonal and Clean Architecture. The principle that high-level modules should not depend on low-level modules underpins both patterns discussed in this module.
Palermo, J. (2008). The Onion Architecture. jeffreypalermo.com
Parts 1-4
Palermo's Onion Architecture (2008) is one of the patterns Martin synthesised into Clean Architecture. Its layered rings and inward dependency rule directly influenced the Clean Architecture model.
Fowler, M. Inversion of Control. martinfowler.com
Full article, 2004
Clear explanation of the relationship between Inversion of Control, the Dependency Inversion Principle, and dependency injection. Provides context for why these patterns are related but distinct.
What comes next: Hexagonal architecture makes infrastructure replaceable. Serverless architecture makes infrastructure invisible. Module 17 covers serverless and edge computing: Lambda cost models, cold starts, Cloudflare Workers, and the BBC iPlayer architecture that combines CDN edge caching with serverless compute.
Module 16 of 22 in Practice and Strategy