One Domain Object and multiple DTOs enter, one solution leaves
Maybe this is why requirements are such a troublesome part of a project. And maybe this is why business system analysts exist.
Unlike as promised, we will not be completing the requirements of the Carbon Copy Whiteboard today. That post has been postponed indefinitely. I have refilled my reservoir of post ideas, and the dryness of subjective ordinal scales is no comparison for what I have planned!
In other words: unfortunately for you, the reading public, I won’t be running out of things to say soon.
That said, I’m surprised that today’s post hadn’t come up sooner. Consider the following.
Suppose you have a domain object, let’s say, Equipment. Equipment has an ID, description, and room location.
You have two different user interface screens on which you need to display a different subset of the attributes of Equipment on each.
Let’s say you have an Equipment Entry Page for regular users, who can only edit the description. Let’s also say that you have a different Equipment Entry Page for admins, who can edit the description and the room. Finally, ID is a systematically-filled attribute.
In a typical design, you’d have DTOs for each page: EntryPageDTO, holding an editable description and read-only ID, and AdminEntryPageDTO, holding editable description and room location and read-only ID.
Finally, assume a typical DDD design, where the DTO is converted into a domain object, sent to a repository, and saved.
Now consider this sequence of events:
- UI asks application layer for an EntryPageDTO for Equipment with ID 5412.
- Application layer asks EquipmentRepository to retrieve with criteria “ID = 5412”.
- EquipmentRepository returns the Equipment object with description = “foo”, ID = 5412, room = Basement.
- Application layer creates an EntryPageDTO with description = “foo”, ID = 5412 from the Equipment and returns it.
- UI updates the description and asks the application layer to save the EntryPageDTO.
- Application layer asks the EquipmentFactory to create an Equipment object with description = “foo”, ID = 5412.
- Application layer sends the Equipment to the EquipmentRepository to save.
That’s as far as this can go. Can you spot the problem?
The original Equipment object had description = “foo”, ID = 5412, room = Basement. After the UI updates the DTO, we are saving an Equipment object with description = “foo”, ID = 5412, room = null. Tragedy ensues.
This never arose on The Project because all attributes were used in DTOs, but this happened to me at work last week. What is the solution?
Suffice to say, no one on the entire Internet has ever encountered this problem, just like every other problem I’ve faced, and now I have to write a blog post about why all the possible solutions suck. Isn’t life fun?
- Application layer cache of loaded domain objects
This works because before you re-construct the partial domain object, you can see if you have it in the cache, and if you do, you can update only parts applicable to the DTO the UI sent you. You don’t lose non-DTO data and you can go on your happy way.
This sucks because this is an entire can of non-domain worms: how big should the cache be? What is your eviction policy? How long should objects stay in? Is the cache per-instance or static?
- Send an extra parameter to the repository indicating which part of the domain object should be saved and which should be ignored
This works because you are attacking the problem directly by telling your data access class which updates to perform and which ones to hold off on. You don’t lose any non-DTO data and you can go on your happy way.
This sucks because it wreaks of procedural goo. It might be nice for two DTOs. But tomorrow it’ll be five DTOs, and a few hours after that, you have 14 DTOs. Suddenly, you’re on your back like a turtle, maintaining a switch-case.
- Load the domain object from the repository a second time before saving
This works for reasons similar to the application layer cache solution, and would work identically, except instead of looking into a cache, you skip this step entirely and load every time.
This sucks because instead of one round-trip to persistence, you have two.
- Have the domain object implement a DTO interface and don’t actually have a separate DTO object
This works because there is no translation from DTO to domain object: change the DTO and you change the domain object. No non-DTO data is lost and you can go on your happy way.
This sucks because it is architecture-specific. What happens if you’re writing a Web Service or your UI is written in VB?
Yuck. Why is there such a huge disconnect between best practices and design philosophies and the everyday languages and tools in this industry?
I ended up going with the application layer cache, but a highly dumbed-down version. To avoid the caching can of worms, I only remember a single loaded domain object.
Every time I load from a repository, save in the cache.
Every time I get a DTO back from the UI, convert it into a partial domain object. Then check to see if you have something in the cache, and if so, update the cached domain object with the applicable updated data in the partial domain object. When that’s done, blow away the cache.
This is all good, except it places constraints on how your application layer can be used by the UI. Namely, if you load Equipment #5412 and then do a save of a new piece of Equipment, #3312, you’re in deep doo-doo. Now Equipment #3312 will be saved as a hybrid of the two.
So now what? This can be avoided if the either UI throws away its instance of your application layer between load-and-save operations, or if the UI promises that when it calls save, it is to save what it just loaded.
Which is the better trade: UI control coupling, or the complexity of caching? It’s a tie: they both lose.
Now wasn’t that better than some boring requirements?