The following started as a comment response to Uncle Bob’s “Active Record vs Objects”, but the comment trail is so littered with spam that I feared it would be lost.
In the article, Uncle Bob illuminates the flexibility cost of Active Record, providing background for its negative impact on The Clean Architecture pattern he presented in: Architecture: The Lost years.
If you’re a long-time Rails developer, you will probably experience two things as you read Uncle Bob’s concerns and watch the video:
- an intuitive “Aha, so THAT’s the problem!” feeling, but also
- a dread “But how else would persistence work in a Rails app?” sense.
Both responses are well-placed; don’t let the conflict stop you thinking! After all, what you’re struggling for is valuable: to have all the dependencies in your application pointing inward toward the domain model.
I don’t have the answers. I’m just embarking on the journey myself. And here’s how I plan to tackle it.
Start by designing your domain model with plain old Ruby objects, with no regard for persistence, making heavy use of Tell-Don’t-Ask. Once your domain model starts to look interesting enough to be worth persisting, then take a look at how you might go about persisting the data from your business objects without attaching additional behaviour to them. At all.
There seem to be three concerns that need to be addressed:
- The Repository,
- the Row Data Gateways, and
- mapping data from the Row Data Gateways into your business objects.
Repository implementations exist. My gut feeling is that the active_record library probably works really well as a Row Data Gateway, as would data_mapper or just about any Ruby ORM. Mapping the data into business objects is a responsibility that must lie outside the business objects themselves if we are to maintain the correct direction of dependencies. But the business objects need to provide an interface to their data, violating Tell-Don’t-Ask.
And so I think our sense of dread stems from the fear of not being able to handle the data mapping concern correctly. Where does this responsibility lie? Observing the Single Responsibility Principle, it definitely doesn’t belong in the business objects or the Row Data Gateways. My guess is that mapping belongs in adaptors that are private to the repository, leaning on a mild Tell-Don’t-Ask violation in the business objects.
Dan Bernier provides another valuable perspective on this sense of dread. In Out of Love with Active Record, he demonstrates that the Repository pattern forces us to think about our applications’ data access patterns in ways that we have not had to think about them with fat active_record models. This thinking is hard, and I think it’ll pay off.
Here are some working examples that have captured my interest and gotten me thinking about how I’d like to solve the problem:
- Shane Isbell’s hand-coded example, Simplifying Ruby On Rails Queries With The Repository Pattern
- A framework that uses dependency injection (light coupling), Curator
- A framework that promotes complete decoupling, Arden
I know of two books that deal with this issue (inter alia). I’ve only read Objects on Rails, but plan to purchase Clean Ruby soon too. The way these authors implement Data Context Interaction to separate persistence out of business objects looks promising.
And remember, the fact that it’s hard doesn’t always mean you’re doing it wrong.