DHH posted some code extracted from Jim Weirich’s talk on Hexagonal Architecture in Rails, and invited people to present alternative solutions. Here’s one of mine.
DHH’s first example satisfies the intent and properties of the hexagonal architecture. It allows the application to equally be driven by multiple clients and to be developed and tested in isolation from its eventual deployment environments. It defines ports for interacting with the application and requires adapters to use the ports.
Look at his example controlller:
I don’t see any dependencies on Rails, do you? I see a dozen lines of code that follow the conventions used in many Rails applications, but I do not see Railties, ActiveRecord, ActionController, ActionPack, ActiveSupport, or any of the libraries core to the Ruby on Rails framework. I see an application that saves employee records. It abstracts the details of how it saves those records, and how it gets its input.
Looking at this bit of code through the lens of ports and adapters, I see a couple ports. The first port saves the employee record. The second port accepts input from a user and communicates results back to the user.
The ports are still a bit abstract at this point, so I’ll define protocols for the adapters to follow:
These specs make it easy for me to write new adapters to work with this application. If I can implement objects that satisfy this protocol, I can extend the capabilities of the application without modifying its core logic, and I can change the core logic independent of the technologies that support the application.
When I write the specs out like that, I’m amused by what seems to be a good bit of work to support 10 lines of code. At first I thought this might be too trivial of an example, but I’ve identified two protocols that together define six methods.
I’ll start off with a persistence adapter. The first one can work in memory to get me going.
Next I’ll make a view adapter to accept input and provide feedback.
Now I need to connect the adapters to the application. I can do this without changing any application code.
By ordering everything right, I can paste it all into irb to run the app.
In 30 lines of code I’ve built entirely new adapters to DHH’s application. I didn’t abstract Rails away DHH’s design decisions meant that Rails was never there to begin with.
It’s not just a lovely bit of Ruby trickery ;) This is what happens when you work towards a hexagonal architecture you design applications that speak well-defined protocols and depend on abstractions. The architecture doesn’t make this happen automatically. You have to think about the architecture and make design decisions to support it.
In terms of hexagonal architecture, the original Rails controller and action fulfill the properties of making application logic available via protocols, and keeping the application logic independent of persistence and UI implementation details.
I hadn’t considered that the view might be the primary adapter and so it makes sense to inherit from it. I think refactoring to make the dependencies explicit will lead to a more natural design.
Inverting the dependencies let me move the application code right to the top. It now purely expresses the application code without any hidden dependencies. You can use the protocol specs to define new adapters easily.
Ultimately, the hexagonal architecture style attempts to guide you toward a modular design style. A modular design style enables powerful software systems that are easy to reason about and change. DHH may have tried “to illustrate the design damage that’s being induced by following this pattern”, but I’d say his example shows how easy it is to apply the hexagonal architecture in Ruby. Hexagonal architecture doesn’t replace your design sensibilities, it augments them by providing a set of factors to consider when designing software.