Once upon a time there was a small but prosperous village. This village had a reputation for being tech-centric. It was populated mainly by enterprise consultants, software architects, and agile coaches. The denizens of the village had grown wealthy and contented from their lucrative careers. And while none of them were bad people, they had a reputation for being a bit cold to outsiders.
One day a pair of itinerant programmers named Susan and Bob wandered into the village, and set up a makeshift camp in an abandoned hackerspace. No sooner than they had unpacked their laptops, then they began taping up posters all over town: “Web Applications: lightweight, fast, and cheap! No big frameworks!”
This quickly got the attention of the townspeople. They gathered at the hackerspace, bemused and a bit disdainful. “There are just two of you, and you claim you don’t use any big frameworks. How do you propose to build useful applications?” “Come and see!” the programmers replied. “All we need is the Active Record pattern!” The crowd scoffed. “How can you build real applications without entities, and beans, and containers, and repositories?” they jeered.
But the two programmers calmly set to work defining simple Active Record classes that mapped directly to database tables.
After a little while, one of the programmers turned to the other and said “you know Bob, the one thing that goes great with Active Record is a good Domain Model to handle validations.” As soon as she said this one of the onlookers shouldered his way forward and said “I’ve written dozens of domain models, let me pair with you on that”. The two programmers happily let him sit in. He quickly defined dozens of validations designed to ensure the application data stayed consistent. As an experienced domain modeler, he threw in some Contextual Validation as well, because domain objects often have different rules in different contexts.
Soon after, as Susan and Bob were fleshing out this domain model, Bob remarked: “I keep running into a situation where I have partial models that need to be filled in if corresponding data exists in the database. I feel like our Active Record needs some logic dedicated to mapping columns to domain model attributes”. No sooner were the words out of his mouth than another audience member piped up and said “you need Data Mapper!” Bob gestured towards a free seat next to him, and the new volunteer sat down and began writing code. After conferring with Bob a bit she also threw in some Lazy Load to avoid excessive database traffic.
Meanwhile, Susan was writing more and more specialized Active Record finder methods, each with hardcoded finder logic. One particular observer was becoming audibly frustrated by her approach the longer she kept at it. Finally, he walked up and said “you’re doing it all wrong, let me show you something”. Susan graciously turned the keyboard over to him, and before long the codebase could boast Query Objects build with a convenient DSL for building up abstract queries. While he was at it, he tweaked the Active Record objects to behave more like Repositories, taking abstract queries and turning them into iterators over data sets.
And so the project continued. As the day waned, one after another village member jumped in to add their own expertise to the simple, Active Record-based application. One added Association Table Mapping so that models could link to each other. Another added Embedded Value for aggregating multiple database fields into a single nested object. A particularly abstract thinker added Metadata Mapping to reduce some of the tedium in writing new extensions to the Active Record classes. Someone else stepped in to help with a Unit of Work, allowing whole trees of objects to be written to the database if any fields had been modified (or “dirtied”) by domain logic.
They worked and worked. Finally, exhausted, Susan and Bob made one last commit, watched the tests turn green, and stepped back from their work tables. The townspeople looked around at each other, and realized that they had been at this task until late into the night. But then they experienced a much bigger revelation: not only had they helped write an app in a day; but Susan and Bob had been right: they’d built the whole application with just Active Record, and nothing else.
That night they ordered pizza, and the whole village sat on beanbags in the hackerspace and discussed ideas for new applications without all the bloated layers they had grown accustomed to. Nobody noticed when Bob and Susan quietly slipped out, and set out to bring their message of simple, lightweight design to a new town.
Storyteller’s notes:
First off, let me tell you the inspiration for this post. I’ve been working on an app of my own, and I’ve been writing my own classes for mapping database rows to domain objects, rather than using any off-the shelf frameworks or libraries. It’s all been very keep-it-simple, only-write-what-I-need, keep-concerns separate-in-small-objects.
And yet today, as I was struggling with trying to understand the interactions in the database mapping layer that I myself wrote, I had a shocking realization: I had somehow managed to conflate Data Mapper and Repository in the same class without even realizing it. Mind you, I’d practically been writing this code with a keyboard in one hand and a copy of PoEAA in the other. And yet somehow how I’d still managed to bash together two distinct concerns (mapping rows to attributes vs. fetching and iterating over rows). It was an embarrassing and slightly humbling moment.
Critiques of ActiveRecord-the-Ruby-library sometimes fall into the error of suggesting that there is something fundamentally wrong with the pattern. Nothing could be further from the truth; for many applications, especially heavily CRUD-oriented ones, Active Record is a perfect fit.
However, it is equally erroneous to equate use of ActiveRecord-the-library with the simple implementation of ActiveRecord-the-pattern. As it stands today, ActiveRecord without any extensions comprises all of the patterns I linked to above, and probably many more besides. All of them are baked together in a way that can make it difficult to know where one ends and the next begins. Most idiomatic uses of ActiveRecord in Rails applications bear little resemblance to the examples of Active Record in PoEAA.
Which, again, is not inherently a bad thing. But newcomers to Rails can perhaps be forgiven when they find the ostensible “simplifying assumption” of Active Record to be less of a simplification than they might have hoped. It also makes it easy to talk at cross purposes when discussing whether and how to use Active Record in a Rails application, since Active Record-the-pattern comprises maybe 10% of ActiveRecord-the-library’s current functionality. Are we talking about a few basic finders and some objects that map directly to database rows? Or are we talking about dependent associations, lazy loading, and arbitrarily complex Arel queries?
I’m trying to work my away around to a moral for this story, but I’m having some trouble finding one. I guess one lesson is that, as with my example above of my handcrafted artisinal locally-grown fair-trade data layer, it’s all too easy to conflate concerns without even realizing it. So we should cut framework designers some slack.
But I wouldn’t even have had the terms to recognize that conflation if I hadn’t read PoEAA. Now that I’ve recognized my mistake I’m pretty excited, because I can see the way to a design that isn’t going to hurt my head nearly as much. (It’ll also be easier to test, but don’t tell anyone that). So I guess that’s the other lesson: read good books!
Which I suppose is a bit of a letdown after all of this build-up. Sorry about that. Want some stone soup?
Absolutely awesome. My Ruby friends, we have a new, slightly more sensical _why writer! Why is it that I read fiction readily, in detail, yet I skim factual text?
ActiveRecord != active record, and the same goes for DataMapper and data mapper, however I do find it less conflated.
I find it awesome that you are trying to see how hard or easy it is to cut a major dep out of your app. AR is a beast. Useful and slow. Kind of like Ruby 😀
I was looking for something data-mapper-related to apply on AR and this post gave me another vision on AR and its “embedded patterns”. Really good post, nice story and writing.