In the second “Is TDD Dead” hangout, DHH says that developers justify hexagonal architectures by arguing that it gives them the freedom to swap out the GUI for a terminal application, or to swap out the database layer with an in memory store. He finds that ludicrous. I can understand why. Although I’m currently working on an application that has moved from Sinatra to Rails, has multiple datastores, and is just embarking on a terminal UI, I admit that this has been rare in my career.
Developers that invoke the “swappable datastores and UIs” argument have likely cargo-culted that argument from someone else. There’s a much more immediate and pressing need to separate your domain from your UI framework and your persistence layer: they change for different reasons.
Consider this: do you want to have to change the objects containing your application’s business logic whenever DHH releases a new version of Rails? If you put business logic in controllers and ActiveRecord models, you run this risk. With each major release of Rails, I’ve seen applications built “the Rails Way” go through this pain.
What about this: do you want to have to change your business logic in order to improve the speed of persisting and retrieving data from your datastore? In my career, database querying has often been a performance bottleneck as application usage increases. Imagine that you’ve had to start denormalizing your data in order to improve performance, or that you’ve had to create new tables or use stored procedures, or shard your data, or start moving certain types of data into high performance key/value stores. If you built your web app “the Rails way”, then you merged your business logic and your persistence logic into the same classes. You’ve let the structure of your database dictate the locality of your business logic. Because of this, as the structure and nature of persistence starts to change in your application, your business logic will have to change with it. I’ve felt this pain too many times.
Uncle Bob defines the single responsibility principle as this: an object should have one, and only one, reason to change. If an object contains both business logic and persistence logic, then it, by definition, has two reasons to change. If an object has both business logic and presentation logic in it, then again – it, by definition, has two reasons to change. Hexagonal architectures try to separate the things that change for different reasons, and collect the things that change for the same reason. These architectures are simply an extension of the Single Responsibility Principle. (Note that I’m lumping Alistair Cockburn’s “Hexagonal” ports-and-adapters architecture in with Ivar Jacobson’s “Use Case” approach, popularized by Uncle Bob Martin).
Over the last six years, I’ve had the opportunity to work on lots of different Rails applications, with lots of different developers, for lots of different companies. Most of these Rails applications were built the “Rails way”; in other words, they largely consisted of controllers, views, ActiveRecord models, and helpers. Most of these applications reached an inflection point as our business logic became more entangled with the framework: velocity slowed, defect-rates increased, and developer happiness dropped. Turning that around took a lot of work. I’ve seen so many developers blame Rails for this.
But who should we really blame for this plague of bad Rails applications? Rails itself? DHH? No. We, the developers, are to blame. “The Rails Way” makes a lot of sense for CRUD applications. But CRUD applications don’t exist in the real world. The REAL WORLD exists in the real world. We build applications with real world business logic in them. For that, we need real world architectures. Rails provides a fantastic set of tools for us. It solves so many problems inherent in web development for us, letting us focus on our problem domain. (Don’t believe me? Watch the beginning of Yehuda’s talk, “Rails: the Next Five Years“). And, since version 3, Rails has actually made it really easy to separate out our business logic from presentation and persistence (ActiveModel + Engines). All the tools exist; we have no excuse not to try.
Hexagonal architectures are not, in any way, an insult to Rails. They are, in fact, an affirmation of what Rails exceeds at (the web), and a collective realization of what Rails isn’t (our application).
Although the spectre of a terminal application may seem ludicrous, I’d bet large sums of money that you already have other interfaces than HTML. Have you written a JSON or XML API for your application? Have you created background workers for your application with Resque or Sidekiq? Do you use a gem like “clockwork” to run scheduled tasks for your application? You owe me money.