The standard three- or four-layer models of application architecture seem to dictate the direction of the dependencies between the various “kinds” of object in the system: The UI depends on the application layer, because the UI “drives” what happens “below”. The application layer depends on the business objects, which do all the domain-specific things. The business objects use (and hence depend on) the persistence layer and comms layer, both of which in turn use and depend on external APIs. It is perfectly natural to read a layered model in this way – indeed, that was its purpose.
ui ---> application ---> domain ---> (persistence, comms)
And it is this very interpretation that has made so many applications difficult or impossible to test. Each of those perceived dependencies ends up enshrined in the code structure, and by stealth the design has cast in concrete the layered model. At which point someone like me comes along an exhorts the developers to improve their test coverage. I’m told: “We tried that, but it takes too long to manage the Oracle testdata we’d need to test the GUI,” or something similar. Or the business decides to switch to a new database or enterprise comms architecture, requiring huge swathes of business logic to be re-engineered.
Alistair Cockburn’s hexagonal architecture model introduces an axis of symmetry that can help us see how to solve these kinds of problem:
Instead of the above-and-below of the layered model, we look at exactly the same application (and exactly the same code) using an inside-outside model. The domain objects sit in the middle, and are connected to the real world by what I call Adapters – a GUI, CGI scripts, a command-line, hardware controllers, a print spooler, etc. Each adaptor connects the domain objects to a different part of the ‘real’ world.
So let’s look at our untestable or non-portable application from the “hexagonal” point of view. (Remember, we’re still talking about the same codebase here: only our discursive model has changed.) The dependencies that have made testing so difficult, and which exist in response to a naive interpretation of the layered model, clearly violate the symmetry of the hexagonal model.
ui ---> (app, domain) ---> services
If we wish to be able to easily port the application to use different services, we’ll need to be able to easily replace the adapters that connect us to those services.
ui ---> (app, domain) ---> new services
And if we wish to test the domain objects independently from the hardware, we’ll need to be able to replace various adapters with stubs, shunts or Mock Objects.
fit ---> (app, domain) ---> mock services
We can’t do this currently because the design violates Bertrand Meyer’s Open-Closed Principle (“A system should be open to extension, but closed to modification“). To solve the problem we need to use Uncle Bob’s Dependency Inversion Principle: We introduce abstractions into the middle hexagon, such that the current adapters implement those abstractions.
ui ---> (app, domain ---> abstraction) <--- service
Symmetry is now restored: every adapter depends on the middle hexagon, and the middle hexagon (our domain objects) is independent of the physical context in which it is used.
adapter ---> middle <--- adapter
Now we can more easily see how to peel off any adapter(s) we wish and replace them with mock objects or different services. The adapter/middle axis of symmetry is the key ingredient in improving both testability and portability of enterprise applications.
Update, 16 aug 05
In TDD Pattern: Do not cross boundaries Revisited William Caputo provides both UML and C# code to illustrate his design pattern for removing the dependency on an adapter. This follows his earlier post, which argues that test-driven coding would naturally tend to produce the de-coupled form of the design.