A Stratified Design means writing all of our objects in this behavior-only style, and passing data classes between them. There are lots of detailed decisions to be made around what exactly those data classes should contain, but for now I'm going to stay at a higher level.
You may be thinking to yourself, "Hey! Those are just layers! I've been duped, there's nothing novel or interesting in this except a fancier name!" While what I'm advocating here is very similar to the traditional layered architecture there are some very critical differences.
- Some definitions of layers include a restriction that lower layers may not reference higher layers. When applied to domain modeling this tends to lead to ridiculous restrictions where your data layer is not allowed to return domain objects because the domain is a higher level concern than the database. A stratified design agrees that lower stratum should not use behaviors of higher stratum, but that doesn't mean they can't share the same data classes!
- Layered architectures usually prescribe an exact number of layers for specific purposes. In a stratified architecture, there aren't any consistent named layers. Instead, there's just a series of classes calling into each other as needed. Each of those classes is defined at some level of abstraction, and calls into it's dependent layers as needed. And that's as prescriptive as it gets.
There are a lot of awesome things that follow from having objects calling other objects in this way:
- Decoupled: clean interfaces communicating with data is about as decoupled as you can get (and therefore insanely easy to unit test!)
- Simple, not complected: each object knows only about the interface of it's dependencies. And it accepts small data classes, and outputs small data classes. There's no static or global knowledge, no god objects. And each object represents one concept defined at a consistent level of abstraction.
- Behavior only where needed: the simplest example is you will only be passing small data classes into your view, not ActiveRecord objects (which expose query and data persistence behavior).
- Somewhat side-effect free: not entirely side-effect free, but because there is little to no shared state, it's difficult to be surprised by a side effect.
- Intuitive: If you do a good job separating your levels of abstraction, you will find that when you are looking for something, it's always right where you expect it. Or if it not right there it's one explicit function call away. Contrast this with OO designs that are riddled with inheritance and misapplied strategy and state patterns... Or compare it to "light weight" Active Record based designs where logic might be in an AR hook, or might be in a service class, or might be in a controller...
- Rich Hickey's Simple Made Easy and The Value of Values
- Bob Martin's Architecture the Lost Years
- David West's book Object Thinking (which I reviewed before)
These don't spell this out exactly, but they contributed certain concepts. And as always, remember that "architecture" is dangerous. Lots of people might be excited about CQRS, but that doesn't mean it should be applied to a mostly read only content management site. And Rails might be an efficient platform for quickly building a web app, but that doesn't mean it's right for building a space shuttle control panel. And the same goes for Stratified Design. The architecture of your application should reflect the nature of your application.
But that said, if you give Stratified Design (or something similar) a try, I'd love to hear about your experiences with it!