There are many lessons I’ve learned as a result of working in a more domain-driven way. My recent work on the UI front has highlighted a few ideas that are just as applicable to other areas of application development (outside the sphere of, say, modeling a complex domain). Here are three things I believe developers should always keep in mind when designing or implementing code.
- Hierarchies require coordination between their roots.
- Uniqueness adds complexity.
- Null holds meaning, but it’s meaning will never be consistent.
Like learning how to write a good unit test, these are simple concepts to explain, but each requires patience and experience to understand really well. (a good pairing partner never hurts either!)
Hierarchies Require Coordination Between Their Roots
A graph of objects often needs to interact with another graph of objects, and while it is initially faster to build direct dependencies between members of each group in an ad hoc fashion, in the long run you benefit from controlling access to those members through the root of each group/hierarchy. I see this as an extension to the principle of encapsulation applied to hierarchies of objects (ala: Law of Demeter).
Consider the general case of one object acting as an observer of another. If there are notable side effects in the handling of an event up or down the graph, it makes sense to put the root in control of the event. Refactoring across the hierarchy become easier, adding additional behavior introduces fewer edge cases (thus reducing the need for more complicated testing), behavior more predictable etc– the only exception (as I see it) would occur when the type of messages being passed around do not affect state at all. Which brings us to our next point:
Uniqueness adds complexity
Stateless systems are invariably easier to grasp and comprehend than stateful ones. While stateful systems have a temporal dimension to them which is largely absent in stateless ones, this is not the only source of such complexity. It is the interaction between two or more stateful systems where we begin to see the huge cost in terms of managing complexity (recall your first experience debugging issues in badly written multi-threaded code to remind yourself of this).
This is why value objects are so important as a concept, and why their use is so prevalent in domain-driven design. While the identity of a value object is determined by it’s state, it’s state is invariant, so it does not have to be managed the way an object with variable state needs to be managed. But the use of VO’s in domain-driven design is just one manifestation of the broader concept: always strive to reduce the number of parts in an application which rely on uniqueness (even if this requires a few more parts added to the whole), whether you are writing domain logic, UI behavior, etc.
The Meaning Of Null Is Never Consistent
It is easy to miss this point, as witnessed by how often I see code which uses null as a substitute for "I Don’t Know…", "Shouldn’t Really Happen…", "Just Satisfy The Compiler…" or "This Is A Placeholder". In reality, null (or nil) is often employed as a mechanism for passing the buck to someone else. Try as you might to ascribe a single meaning to it for all cases in your API, client code will feel free to redefine this meaning on a case-by-case basis, and its meaning will evolve (or devolve, as the case may be) into code which looks defensive, but behaves unpredictably. Avoid returning null when possible– strive for clarity instead. While in other languages (such as Ruby) it is possible to add behavior to the concept of null, thereby making it’s contract with the rest of the code a bit clearer, I’d still choose the alternative, and use something else.
That’s it– three little things that come in handy just about anywhere. My initial intent was to write a line or two for each section, but as you can tell, there is so much to say on each topic, and all of this just scratches the surface.