A Conversation with Badri Janakiraman about Hexagonal Rails

The recent conversation I’ve been having with Kent Beck, David
Heinemeier Hansson about whether TDD is dead has spawned plenty of
other conversations. One such exchange was with my colleague, Badri
Janakiraman, one of our senior developers at ThoughtWorks. Badri has
over a decade of experience, working both in Ruby on Rails, but also in
other development stacks with Java and .NET. For the last six years
he’s been working in Studios, our product division, where much of his
time has been with Mingle, our project collaboration tool that is a
long running Rails project.

Since our discussions were so interesting, I thought it would be fun
to capture them on video. So we scheduled some time together for a
video chat, which I think you’ll find an interesting viewpoint into the
role of Hexagonal Architecture in Ruby on Rails, and an useful
complement to the “Is TDD Dead” discussion.

1: Active Record or Data Mapper?

We introduce the notion of Hexagonal Architecture, and Badri
describes the trade-offs between using Active Record or a Data
Mapper to push the database outside the hexagon.

more…

Minutes

Badri began by explaining the notion of
Hexagaonal Architecture, originally
laid out by Alistair Cockburn.The
main idea of this architecture is that your domain is a
self-contained entity with no dependencies on external
components. This allows you to reason about, and test, your
application independently from those components.This leads to the question of what is external and what is
internal? The descriptions of hexagonal architecture in Rails
usually consider the database as external, but that isn't
a given.The original article also considers driving your application
through different forms of UI.

Martin drew the relationship between hexagonal
architecture and the notion of a layered
architecture. A common layering is some variation on having
layers for UI, domain, and data source: with dependencies
running UI → domain → data source. Hexagonal architecture
changes the dependencies between domain and data source so
your dependencies run: UI → domain ←︎ data source.When Martin wrote P of
EAA, he described this approach as the Data Mapper pattern, which
is in contrast to Active Record which ties
the domain to the data source.

Martin asked Badri how he
characterized the differences between Data Mapper and Active
Record. He replied that it's all about the kind of application
you need to build. In his early career he built applications
with complex business logic such as leasing and insurance.
When the domain has that kind of complexity you need to
separate it from database concerns.

In the last six years or so, he's been working
with rails on product applications with much simpler domain
logic. For example Mingle
is a project collaboration tool that helps you manage your
backlog with a card wall metaphor. Since ThoughtWorks's
philosophy is that the tool should not constrain a team's
process, we need lots of flexibility for the data, required
user defined fields. The usual approaches for user-defined
fields don't work very well, so we dynamically alter the
relational schema to add custom fields. This has worked very
well and ties the domain objects to the database, bringing the
database into the heart of the application.Martin summarized that the theme here is that Data Mapper
should be used when you want domain model and data source to
evolve independently.

Badri continued by saying his pre-rails work involved
cases where the application developers didn't have
complete control over the database structure. This makes Data
Mapper more attractive. Data Mapper gives you more isolation
from the database, but it isn't complete isolation - you still
need to take into account data management concerns, such as
loading the same data with different domain model objects
depending on the particular need.

Badri described how Mingle works with a few different
databases (Postgres, Oracle, and at one time MySQL). The fact
that the unit tests hit the database greatly helps portability across
these databases - contrary to the usual notion that says you
need isolation for this.

Martin summarized the trade-off between Data Mapper and Active
Record by saying that the reasons to use Data Mapper are if
you don't control the database (for instance when you are
using an integration database) or if you have complex domain
logic. But if these forces aren't in play, then Active Record
is simpler (and hence better). We point out that the size of the database isn't a factor
here: Mingle is a 50 KLOC app with over a hundred database
tables, yet works well with Active Record.

Martin asked how long it takes for the tests to run.
Badri replied that the tests run in around ten
minutes or so with tests running in parallel with multiple
browsers and databases. The team finds a ten minute commit
suite is fast enough to maintain flow. They commit into a pending head.

2: Rails: Platform or Suite of Components?

When working with a rich framework like Ruby on Rails you can
treat it as with a platform or as a suite of components. Badri
discusses the difference between these and we discuss what
trade-offs go with the decison.

more…

Minutes

We started this conversation with Martin asking what the
attitude towards Rails (and other similar frameworks) should
be. Should we treat them as platforms, accepting them, warts
and all? Or should we treat them a suite of components in
which you pick the parts of the framework you like and leave
out the parts that you don't.Badri responded by saying that while Rails has come quite a
ways in being more modular, it is possible today to subset
Rails more easily than to swap out whole components. For
example, one could leave out Action Mailer in an application
that doesn't need to either send or receive emails. One could
pull in Action Support into a standard Ruby application and
get a lot of the convenience methods and core-extensions that
it provides. That said, one cannot, for example trivially
replace the Rails request-response dispatch cycle with an
evented system- like the one in Node.js for example. And this
is because Rails is built to support a very specific kind of
application.Martin chuckled, referring to the trope that
Rails is a framework meant to build Basecamp.

Badri countered by saying that the sweet spot for Rails is a
deeply webby application that talks to a relational datastore.
In other words, your application has limited
number of users and regardless of whether it is humans using
your application through a web UI or other machines
interacting with your application through a REST API, the web
was not an accidental element.Badri further speculated that perhaps the problem is that people
decide on using frameworks too early. Frameworks take away a
lot of design options in return for a lot of convenience- and
that means that perhaps we should understand how we would want
to design our application and then pick a framework that
doesn't prohibit those design choices rather than choosing a
framework first and having it get in the way of how you would
like to design the system

Martin replied that the choice to use the framework and design
your application one way might have been made some time ago.
That said, the framework designers might choose a different
road and you might find yourself stuck in a corner. Martin
then summarized our conversation so far by saying that if you
are in the sweet spot for the framework, it makes sense to
embrace it as a platform rather than spending the effort
upfront to isolate oneself from possible future change.Badri agreed with this summary and said that some people find
the additional layers they put in to be beneficial in guiding
their design thoughts. That said, he personally tends to lie
on the side of using Rails as a platform.

His personal preference for using a framework as a platform
came from experiences in trying to circumvent frameworks in
the past. Using the example of rich-client frameworks in .NET,
He recalled the one time when he worked on an application
where they attempted to not use two-way data binding. Due to
how some members of the team felt the application should be
tested, they tried to create a humble
view by not using the data binding mechanisms. This led to
a considerable mess because the framework was not designed to
be used that way and we ended up writing far too much code and
re-inventing major chunks of the framework. This is the kind
of cost that needs to be understood by teams who wish to run
against the grain of a framework. Perhaps the cost is
sometimes worth it, but the fact that there is often a huge
cost is unavoidable. He added that teams might wish to pick
best of breed libraries and building a-la-carte applications
if they find themselves wishing to make significant changes to
a framework.Martin reiterated that the point is to make an
informed choice to use a framework as a Platform or as Suite
of Components driven by need- and not by blindly going
full-embrace of either mode without having taken into
consideration the costs and benefits of either approach.

Martin asked if there is value in frameworks
making certain design choices for you and if that enables new
members of the team to get started more quickly.Badri responded by saying that this is an advantage of
frameworks in general - including ones such as Spring in the
Java world. He claimed that he personally found the common usage
patterns that people follow when using a framework to be more
important- citing the example of how back in the Enterprise
Java world, around 2001, almost every application would have
it's own implementation of where transaction boundaries lay
and how they were implemented. This was a something that he had
to learn over and over in each application he worked on. In
Rails, on the other hand, there is a common pattern of
starting transactions and rolling them or committing them in a
filter that wraps every request-response cycle. This sort of
learning is useful to be able to carry from one Rails codebase
to the next.

Martin called out for people not to push frameworks
into places which are not their sweet spot.Badri added that this while this point is perfectly valid,
Rails certainly does leave the door wide open for all sorts of
design choices in places where the framework does not have an
opinion. He illustrated this with a couple of examples from
projects he was involved in.The first example is where they would fetch
commit data from a version control system so that it could be
shown in the context of other data in the application. Due to
the needs of having the application work without this
functionality and also the need to have data from multiple
version control systems all be pulled into the application,
they designed a port which defined the point of entry for this
information into the system. This port became the single point
for adapting all incoming data on by appropriate use of
adapters for each kind of data that would arrive onto this
port.

The second example was of two way communication where they
built a Jabber chatroom bot that would pull in messages in
team chat rooms that were relevant to stories that were being
worked on, and attach them as a discussion thread to the story
in the application that managed the backlog. Once again,
because this functionality wasn't core to the application and
they needed to let individual teams turn this functionality on
or off as needed, they implemented a gateway that took care of
all communication between the chat room and the core
application which was completely oblivious as to whether this
functionality was even configured. This gave them a good level
of isolation for the core of the domain logic.

Martin summarized these two examples by saying
that using Rails is not about avoiding avoiding isolation
completely. He empasizes that you don't want to isolate
yourself from your platform- but you do want to isolate
yourself from external pieces.
The distinction between what constitutes external and what
constitues internal parts of your application brings us back a
full circle to where we started our chat with Hexagonal
Architecture pattern.