At various LRUG meetings over the last few months there’s been talk about how Rails apps haven’t been as maintainable as they were in the early days. There seems to be a general sense that the “Rails way”, however you define that, can only take an app so far. After that, the maintenance burden becomes a lot higher and forward progress seems to hit a wall. Most of the pain seems to be around fat models and the huge reliance on ActiveRecord as the sole source of domain logic.

To address this, there have been attempts to add extra conventions or means of guiding how an app is built. A few examples include Avdi Grimm’s Objects on Rails, Jon Leighton’s focused_controller and James Golick’s objectify. These approaches provide different ways of building Rails apps in a more object-oriented way, with the aim of providing higher quality, more maintainable code.

Another approach that caught my eye was based on Uncle Bob’s Use-case centred design post and Architecture: the lost years keynote. He also uses this in the payroll example in “Agile Software Development: Principles, Patterns, and Practices.” The idea was also explored in Matt Wynne and Steve Tooke’s Hexagonal Rails talk at this month’s LRUG. The central premise of the approach is this: Rails is not your application.

To be clear: this post (or series of posts) is not about bashing Rails. There are so many things that it does really well. It takes a lot of the pain out of web development. The fact that routing and dispatching, security, parameters, sessions and the nitty gritty of HTTP are taken care of for you makes web development a whole lot more pleasant. And that’s without even talking about views: if I never manually write an HTML form again, it’ll be too soon. I’m just really keen to see if there’s a way to use the best parts of Rails while keeping your domain – your actual app – separate.

So, last Thursday, at our monthly notonthehighstreet.com hackday, Roland Swingler and I tried to build a holiday request app by starting with a domain model completely separate from Rails and persistence. By the end of the day we had built a tiny sliver of an application, from a Rails frontend through to a database, with our plain old ruby domain sitting independently in the middle.

I’m hoping to be able to release the code soon, and there’ll be more posts about the details of what we did when that happens. However, there are some general themes that I think are interesting even without the code.

The general structure of the app was a gem which contained all the domain logic, a gem which contained an ActiveRecord version of our database abstraction and a Rails app which included both of the gems. The domain gem fell into 3 main parts:

  • entities (HolidayRequest and Employee) which formed the domain
  • actions (AddHolidayRequest) which was the use-case for, um, adding a holiday request
  • a HolidayRequestRepository which stored and retrieved HolidayRequests

The first thing that struck me when building the domain gem was how fast the tests were. Compared to working in a mature app where it feels like epochs have passed before the tests even start running, the instant feedback was revelatory. Refactoring became simple. Verifying new ideas became penalty-free because you could adjust in seconds. In order to keep things fast, we created an in-memory version of our HolidayRequestRepository so we weren’t relying at all on a database.

For the Rails app, we had a controller that read data from a form and called the AddHolidayRequest action. The result of that just rendered or redirected as appropriate.

Boilerplate was a big thing we faced when we tackled the integration with Rails. A lot of the day felt like reinventing the wheel; Rails gives you so much that deliberately not using it feels like shooting yourself in the foot. One of the interesting points where this came up was when we wanted a form for one of our objects. form_for makes form building a lot nicer, but I didn’t want to include ActiveModel in our POROs that made up the domain. Instead, we created a separate object which used ActiveModel and mapped our domain objects to it. While this left our domain pure it added extra manual work and some duplication which has to be managed. I haven’t done enough of this mapping to find out if it’s too time-consuming or too unmaintainable to be practical, but I’d like to do more to find out.

Interestingly Roland really didn’t like this approach, because it reminded him of his Java days where he seemed to spend a lot of time just wiring together bits of framework. But I don’t think the mappings to form change too much over the the lifetime of an app so it may not be a big deal. While there may be the immediate pain of having to create the mappings, the lifetime maintenance burden may be lower because we’re dealing with smaller objects that can change independently.

My thinking on this is partially inspired by Rich Hickey’s talk, Simple Made Easy. This may be a blog post in its own right, but just because something is easy doesn’t mean you should do it. Rails makes everything almost frictionless in the short term. Just including ActiveModel into your objects is easy, but it’s not simple. There’s a whole burden of the complexity added that has to be justified. Mapping two objects is simple, it’s just a bit more boring and adds some duplication. Maybe the tradeoff isn’t worth it. But maybe it’s like being forced to eat your greens: in the long-run you’ll be glad you did. It may also be possible to build a simple abstraction around the mapping between POROs and ActiveModel to reduce the pain further.

I was pleasantly surprised towards the end of the day when it only took 30 minutes to create an ActiveRecord version of our HolidayRequestRepository. This was because we’d included no logic in our in-memory version so we could use ActiveRecord out of the box. As the app grows there’ll be more complexity around the mapping between domain objects and the database but I’m quite pleased how it’s turning out.

There is a lot more work to be done on the app; we didn’t actually get much functionality in. There are a lot of decisions that we made just to get the whole slice complete in one day that I’d really like to revisit. I want to see what happens through the whole lifecycle of creating, editing and destroying objects and what happens when the domain gets complex. At some point there may well be performance issues that will really strain the separation between persistence and the domain. Will the mappings I discussed earlier prove burdensome?

A lot of questions, not a lot of answers. The application was really an exercise in seeing how far we could take things in a short period of time. Not all of the ideas may work, but it was a really interesting day.

Hopefully I’ll be able to make these ideas more concrete with actual code soon. Until then, I would love to hear of other people’s experiences trying the same Rails separation, to see the successes others have had or the things that they found really hard. So please leave a comment here, or find me on twitter.