I suffer from OCD: Odorless Code Dreamer
It sucks. It sucks, I say.
”exports the internal state of the object”
Of course, “exportTo” and “importFrom” are not part of the domain. Fine. But it seemed like such a good idea instead of getters for everything so we could display and save domain objects. You ran the risk of exposing implementation details because you might not be able to throw out the implementation of an object and replace it with a new one and not affect any other code.
But when I got around to reconciling the Holub Builder with DDD Factories, I ran into a snag.
In the example above, the exporter object would end up needing to implement interfaces for exporting Rooms and GeneralEquipment. That’s fine.
It gets weirder, though, when you need to import.
”imports internal state from an importer, once and only once per object”
roomDS := (importer getRoomData).
equipmentList := OrderedCollection new.
[importer hasMoreEquipment] whileTrue:
equip := importer newEquipmentInstance.
hasBeenInitialized := true.
I’d like this out of the domain object and into a Factory class. That implies that the importer can’t import the individual bits and pieces of both Rooms and GeneralEquipment. It would have to import an entire GeneralEquipment.
Now you have an exporter that exports individual attributes of Rooms and GeneralEquipment, and an importer that doesn’t. That smells already.
What happened to our love, it used to be so good…
I could just keep it that way, and I guess it works. But the original intent of implementing the importer/exporter interfaces was for something like, say, a UI screen. Then you could simply import the contents of or export the domain object to the UI screen without the Room object knowing anything about where the data was coming from or going to.
Now it doesn’t work. I don’t want my UI screen to, in one direction, know about domain objects, and in another, only care about strings, as it should.
To get around this, keep the importer and exporter interfaces sweet and simple. A Room contains a room data structure and a list of GeneralEquipment. That is what it exports. It is up to the exporter to then export each individual GeneralEquipment. Ditto for importation.
Now stop and look at what we have. Essentially, the importer and exporters don’t have any more of an advantage over plain old (domain-coupled) getters.
Now it sounds like I’m in a pickle. On one hand, I may as well go with domain-coupled getters instead of the Builder. On the other hand, that might not cover all of my data and UI needs because the domain object might have internal non-domain-related implementation details that might change in the future.
Hold it. Hold on. What? Re-cue that tape, Mr. Blogcast Engineer.
On the other hand, that might not cover all of my data and UI needs because the domain object might have internal non-domain-related implementation details that might change in the future.
Why would I be thinking that a “domain object” would have “non-domain-related implementation details”? A domain object, by definition, contains only stuff related to the domain. This is a contradiction.
If you know what you’re doing, which I clearly do not, then the only implementation details in a domain object will be domain-related. Therefore, the only reason they would change is because your domain changed. Therefore, it is alright to have getters for all of them because those getters are domain-related. You could throw out the domain object’s implementation and replace it with a new one.
Well, almost. I might look stupid, but I’m not dumb. Every domain object has details that aren’t related to its domain by virtue of being in a computer program. Strings, Lists, and Maps are programmer jargon.
You have to do it correctly, and the DDD Sample Project woke me up.
The truth is a complicated shamble of broken glass
It seems like the key is avoiding Primitive Obsession. If a Room consists of an ID, name, and list of GeneralEquipment, then it shouldn’t have members of type Integer, String, and List<GeneralEquipment>, respectively. If you did and you needed to change Integer to Long, String to StringBuffer, and List<GeneralEquipment> to Map<GeneralEquipment>, then other code will be affected.
What are the getters for, exactly? Mainly for display on the UI and saving in persistence. When either of those happen, it is usually done with Strings. But I have been hesitant to Stringify everything.
You certainly don’t want String, String, and List<String>, because that doesn’t express the domain model. Suppose an Order has a Price. Plus, if you needed to do mathematical operations on this price, but the only way you could get at it was through a String, you’d have to convert it every time. That sucks.
Are we stuck? No. Encapsulation to the rescue. You’d want something like RoomID, RoomName, and EquipmentList. For display purposes, each could have an asString method. For all other purposes that involve logic, design them as regular objects.
Holub’s point may not be so tenuous after all. Now if instead of Integer for the Room ID I wanted to use Long, I could do it without sweeping effects throughout the code. UI / persistence wouldn’t be affected because they both call asString. Other business logic wouldn’t be affected because they’d, presumably, only be using methods of the RoomID object to get their work done.
Holub almost got this right in his original article:
First, as I discussed earlier, it’s okay for a method to return an object in terms of an interface that the object implements because that interface isolates you from changes to the implementing class. This sort of method (that returns an interface reference) is not really a “getter” in the sense of a method that just provides access to a field. If you change the provider’s internal implementation, you just change the returned object’s definition to accommodate the changes. You still protect the external code that uses the object through its interface.
The important point that was lost here is that when you legitimately need data out of such an object, a conversion from the internal type to a standard type is needed. Indeed, Holub does this with the Builder pattern he presents, but this misses the point because sometimes code legitimately needs a non-String representation to work with, like summing up all of the memory in a collection of PC objects.
Final (yeah right) thoughts
That said, I think these benign getters should return copies of their fields as much as possible. If the objects they return are not immutable, you can end up mucking with an object’s internals in ways that are not supported by its interface.
Another thing I want to make clear is that while carefully-thought-out getters are fine, brainless setters are evil. By “brainless”, I mean setters for every domain-coupled field, following the same principles as the getters do. You should only have setters that have domain meaning.
If something doesn’t change after the object is initialized, that constraint has to be enforced somehow. That could be via using constructors and no setters for those fields, or by keeping a hasBeenInitialized flag in the object.
What you are witnessing, ladies and gentleman, is the plight of a programmer going through a coding phase that appears to be permanent: Odorless Code Dreamer Syndrome, better known as OCD.
|Announcer: You’re reading the EIP web-ring.|