Love For Lazy Load
My love affair with domain-driven design has not been all roses and chocolate. I don’t know if this is the norm in other engineering disciplines, but in our perverse world of software, there is always this tension between the right way to design something and the efficient way to design something.
My primary complaint with the Evans book is that there aren’t enough practical examples to illustrate concepts. I’ll grant you that this was likely not a goal of the book. Indeed, the Nilsson book fills the void.
(I mention this book so often, you’d think it was the Bible)
The horrible truth, though, is that no project can be completely and purely domain-driven. The expressive and encapsulated world of objects is built for expressing concepts and encapsulating data. Performance concerns are the realm of another sorcerer.
Often, that sorcerer is a relational database. Tools help with that, though. A more sinister sorcerer lies within object land itself.
On some teams, this sorcerer is caching. A so-called best practice, in the name of pleasing the Mighty Cacher, is to design objects for caching: separate objects with shared state from objects with user-specific state.
(How long until this guy starts showing up in my “visions“?)
To me, this means designing your object model around a technical service. This detracts from the expressiveness of the model.
Another sorcerer actually lays within the Evans book itself:
Whether to provide a traversal or depend on a search becomes a design decision, trading off the decoupling of the search against the cohesiveness of the association. Should the Customer object hold a collection of all the Orders placed? Or should the Orders be found in the database, with a search on the Customer ID field? The right combination of search and association makes the design comprehensible.
The implications here are that you can gain in both performance and clarity: performance by not forcing an entire collection of Orders to be saved or loaded per Customer, and clarity by avoiding “entanglements.”
To me, this means designing your object model around, yet another, technical service. This too detracts from the expressiveness of the models.
I know this is engineering, and I know there are trade-offs. But at what point does a trade-off become a cop-out? Are these two trade-offs really necessary?
I say they’re not, and the masked avenger that will deliver us from a muddied domain model will get here just as soon as His Laziness can get off the couch.
(I’d be more apathetic if I weren’t so lethargic)
I’m talking about, of course, the Lazy Loading pattern. This pattern can let us have our cake and eat it too, at least in the two binds I described.
First, let’s talk about the caching problem. Let’s say you have one object, A, composed of two others, B and C, and let’s say object C is the cachable object. The design edict would have us, in the name of caching, do away with object A and have objects B and C in our model instead, no matter how disjointed the concepts they both encapsulate are.
I say phooey on that. Keep object A as it should be, and lazily load object C with Lazy Initialization. The lazy load mechanism can check the cache first for the correct instance of object C. If it doesn’t find it, hit the persistent store, load the instance, throw it in the cache, and finally give the reference back to object A.
This way, I get domain model expressiveness by keeping object A in its natural habitat, while at the same time avoiding caching parts of object A that should not be cached.
Next target: associations versus repository searches. Phooey! Model associations wherever they naturally occur. When I read the code for the definition of your class, the modeled associations should be explicit in the code as an array, ArrayList, HashMap, whatever.
Use a Virtual Proxy for that Order interface. Wrap the real, expressive Order object in an OrderProxy that has the order ID as a field too (this keeps lazy loading stuff out of the Order domain object). Now when Customer objects are loaded, they still have a collection of Orders, but unbeknownst to Customer, they are merely IDs. Only when one of those Orders is accessed is that Order loaded.
We keep domain expression. We keep performance. As for the entanglements — I don’t agree that they are a problem, as long as you allow repository access to aggregates; in other words, some globalness, where appropriate.
I’m not even opposed to aggregate roots holding references to other aggregate roots; this expresses your domain. If you are confined to this sort of framework, then you are indeed entangled. Globally accessible repositories are your escape hatch.
Why doesn’t the Lazy Loading pattern get more attention and love? Sure, His Laziness might be a fat white cat slovenly laying on a couch drinking a beer. But, it is one of the patterns for which I find the most practical use. More importantly, it can have benefits that both the project team and the users can observe and appreciate.