When you start a new rails app, it’s fun. It’s exciting. It’s fast. It’s flexible. Rails along with a handful of community gems do so much heavy lifting for you that within an hour you can be up and “sprinting”. Not just with a server, but with a number of key issues pretty much solved. Adding in devise with omniauth gives you near instant integration into the complex flow of OAuth 2. Add in Foundation 5 and you’ve got a great responsive & flexible HTML framework. With very too much custom code, you’ve got a great base on which to build. However, if you are like most rails software engineers & teams, this is when you start downhill into the land of the monolith.
The monolith serves you well for a period of time. It allows you to move quickly. It allows you to learn about the app you are building and make the domain decisions over-time. You can release a highly functional app and get feedback from your target users. Life is great – you’re iterating, releasing, learning and improving. The process is addictive and the product evolves, sometimes moving almost 90 degrees off from where you started.
However, soon the cracks begin to show. Soon you are a little afraid to go back into an area of the app. Then a second scary area appears. Tests are getting slow. Everything’s working and performing well enough, but somethings definitely wrong. Models are getting bloated. Controllers contain complex interaction with models. Managing different clients becomes a bit of a juggling act because the server-side design has taken on some of the custom business logic for a particular front-end client.
It’s not that you haven’t been trying to break things down into modules and haven’t tried to follow other code practices that help improve your CodeClimate score :) The problem is that ruby & rails makes it so damn easy to get things done that so often you opt for a quick fix, rather than consider when it makes more sense to refactor the responsibility boundaries of your systems of objects.
So, you find yourself in the position of maintaining and further evolving a system that has a good amount of “scar tissue” and feature bloat. One option is to try and kill features that have fallen out of use. This is easier said than done when different parts of the business are still hoping to make them work eventually. So what “architecture rehab” is there out there to get you back to your “agile” fighting weight?
The concepts and theory we need is not new. They’ve been around for more than a decade in a “named fashion” and the underlying patterns have been around even longer. The rails community has come to the realization of the need for this medicine all around the same time (give or take a few years). The first major talk on the subject came from “Uncle Bob” in 2011 (video). Oh how I wish I had paid closer attention when this came out.
Matt Whynne followed up a year later with a talk on “Hexagonal Rails” (video) which was based on Martin Fowler’s “Haxagonal Architecture” from 2005 (article). Most recently, Fowler wrote about Microservices this year (article – 3/24/14) and since then Carbon Five has produced a variety of related posts with the most valuable being “An Incremental Migration from Rails Monolithic to Microservices” (article).
If you haven’t read and watched each of these works, then go do it now.
The underlying theme or key across all of them is we need to be spending more time carefully designing our domain system as a stand-alone set of plugins or services and less time wrapping our domain logic around the pillars of frameworks like rails. It’s time to rethink how we think about our application architecture and put tools like rails where they belong – in the toolbox. Their purpose or benefit is to help abstract or automate the communication layer between our application and outside clients & resources. But rails is not and should not be the center or core of our application.
For the last two years, I’ve intuitively known what we were doing wasn’t quite right. Simply extracting functionality into utility or service classes helped, but it wasn’t enough. All you did with those choices was spread the complexity out into more and more classes, but the underlying coupling remained – with the business objects knowing too much about how the persistance worked and also about how the client would consume it.
To me, the way forward is getting much clearer. We need to focus on each part of the architecture and make sure it’s handling it’s responsibility well and not taking on that of others. We need to separate out the domain and business logic of our app so it is well isolated in a lean set of systems at the center of our app with the persistence layer properly separated.
The next steps for me involve another reading deep dive. I have a series of books queued up:
- Domain-Driven Design: Tackling Complexity in the Heart of Software
- Patterns of Enterprise Application Architecture
- Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions
- Implementing Domain-Driven Design
- Practical Object-Oriented Design in Ruby
- Microservices – 72 Resources
In addition there are more gems and community repos I want to unpack and learn about:
- Wisper – https://github.com/krisleech/wisper
- Barrister RCP – http://barrister.bitmechanic.com/
- https://github.com/dwhelan/hex-ddd & https://github.com/dwhelan/hex-ddd-examples
Lots of work to do. One of the hardest things to do when you are in the research & learning phase is to know when to put down the books and start “do-ing”. That time is nearing but I feel I need to get a little more familiar with the underlying design & architecture principals of DDD and think more about what that means for a web app.