It’s my software stack, with complexity!
It’s not an unusual observation that there are two major philosophies about designing Rails applications, sometimes called the “Core” and “Prime” stacks. (Specifically, the name Prime stack comes from Steve Klabnik, though he calls the main stake the “Omakase Stack”, from DHH’s Rails is Omakase post)
The Core stack is based on the way the Rails core team recommends Rails should be used, and is how Basecamp uses Rails, at least based on David Heinemeier Hansson’s public descriptions. The Prime stack doesn’t have a single owner, but is based on a lot of secondary writing about Rails.
I thought it’d be useful to just describe the two approaches, as best as I understand them, and try to determine where they really differ.
The Core and Prime stacks start with different assumptions about software:
The Core stack sees simplicity in fewer classes and fewer non-Rails structures in the app. The Prime stack sees simplicity in more layers, but smaller and simpler classes.
The Core stack doesn’t see the value in encapsulation or the Single Responsibility Principle as a goal in and of itself, and will trade encapsulation for convenience. The Prime stack sees encapsulation as a positive goal and will forgo some Rails conveniences to get it.
The Core stack is more likely to see Rails as part of the application, and the Prime stack is more likely to treat Rails as an application detail. This is, of course, true. Basecamp does have a different relationship with Rails than pretty much every other application, changes to Rails are extremely unlikely to break Basecamp.
A lot of the difference is a question what the “it” means in “You Ain’t Gonna Need It”.
A Core stack developer would argue that the thing you aren’t going to need is extra layers, that most of the time they are unnecessary, and by keeping the rest of the code multiple-layer free, you are better able to manage the truly complex parts of your application.
A Prime stack developer would say that the ”it” that the application doesn’t need is shared knowledge between objects. By keeping the application parts separate, the Prime developer would say, you are better able to manage the truly complex parts of your application.
A Simple Workflow
When I added payment logic to the sample app for my upcoming web payments book, I used Prime structures. The controller does very little work, the bulk of the business logic is in a process class called CreatesPayment. There’s a separate object that is basically a wrapper around the Stripe API object, and the ActiveRecord model is limited to some persistence logic. Despite the extra objects and writing a bit more code, I claim this is simpler I can think about the logic in isolation without worrying about API implementation details, and vice versa.
A Core stack developer would think this architecture is ridiculous, and that my claim to simplicity is absurd. In a Core stack application, the business logic code would likely be in the ActiveRecord model. There might be a little more logic in the controller, or there might be a separate RESTful controller just for creating charges. (I think, but am not completely sure, that the Core stack is more aggressive about creating multiple small controls to keep the actions RESTful.) The Core stack developer would claim this is simpler because there are fewer objects, probably fewer methods, and probably less code over all.
And the crux of the argument is that over time, the Prime stack developer argues that the isolation and extra code makes the application easier to change, and the Core stack developer argues that the more straightforward code makes it easier to add complexity when complexity comes a-calling.
My experience makes me sympathetic to the Prime argument, but it’s not a slam dunk. And as I explain the code architecture in the book, I can feel the argument, the relatively simple logic of the sample problem doesn’t necessarily show the Prime stack in its best light.
DHH’s stance on Test-Driven Development is completely consistent with the rest of the values of the Core stack. DHH doesn’t argue that testing is a bad idea, he argues that full-stack integration testing to validate behavior is most of what you need, and that writing unit tests first to drive the design is a waste of time.
Given the way the Core stack builds applications, this makes perfect sense. If you are not concerned with encapsulation, then the costs of writing unit tests go up and the value of writing them goes down. If you are committed to using already existing Rails constructs then you are not particularly interested in using test- driven development to explore your design space and derive new classes, again driving the value of unit tests down.
That’s not a set of tradeoffs that I find comfortable, but it is a stable and consistent design aesthetic.
My personal preference is to use Prime-like structures, my experience is that separating out parts of the code tends to be clearer. It’s not perfect — complexity tends to be complex — and I definitely have code that may have gone too far.
As with a lot of software the unsatisfying answer is “it depends, but you should understand the tradeoffs”. The more complex you think the logic is likely to get, the more you will probably like having the extra places to put it. But if you predict wrong, then the extra abstraction can be painful.