We now arrived at the last post of this series about Domain-Driven Design (DDD) patterns…
DDD Concepts and Patterns – Supple Design
Today I would like to introduce the concept of supple design. Previously through this series about domain-driven design (DDD), we climbed up the abstraction hierarchy from class level patterns up to the context map. With supple design, we are back on the class or function level. Some synonyms of supple are agile, flexible, or bendable but also smooth. Supple design is obvious, doesn’t obscure things.
Let’s split the concepts of supple design up into two parts. This first post is about Intention-Revealing Interfaces, Side-Effect-Free Functions, and Assertions. Next time I will introduce Conceptual Contours, Standalone Classes, Closure of Operations, and Declarative Style of Design.
Intention-Revealing Interfaces
As you may have heard before and experienced yourself, the complexity we humans can handle well is limited. Software design acknowledges this. Concepts like information hiding and abstractions have been invented by smart people long ago.
However, these concepts are only useful if we don’t need to know the details of abstraction, for example. We need to design our interfaces in a way that reveals their intentions to their users. Callers shouldn’t need to check the implementation to understand what an interface method does. If the intention is clear cognitive load is lowered, and they are able to think about other things than our (maybe perfectly crafted) implementation.
For example, take a look at the following interface of a class that stores a user-to-terminal mapping.
public interface UserTerminalMapping { void setTerminalForUser(String user, String terminal); String getTerminalForUser(String user); }
How do you remove a mapping with this interface? Experience or intuition may tell you that we need to call the setUserForTerminal
method with null as the argument for terminal. Nevertheless, we cannot be sure without looking at the implementation. Maybe mappings expire after some time, or the author of the interface forgot about the case when we need to remove a mapping.
A solution could be that we add another method that removes the mapping like this.
public interface UserTerminalMapping { void setTerminalForUser(String user, String terminal); String getTerminalForUser(String user); void removeMappingForUser(String user); }
Side-Effect-Free Functions
The concept of Side-Effect-Free Functions has become widely known through the past ten or fifteen years with functional programming. It has various benefits if we know that a function doesn’t change any observable state of the system.
DDD separates queries and commands strictly, which takes us a long way towards a supple design with side-effect-free functions. Another technique that Eric Evans describes in the original DDD book (Eva04) is to extract a value object. Value Objects are immutable by definition. Therefore all of their methods need to be queries. If we can replace a change of state with the creation of a new value object, it should become simpler to reason about the design.
For example, let suppose we have an invoicing module where we can add discounts on individual invoice items. The implementation is a method applyDiscount(Amount amount)
which adjusts the item’s amount by the given value.
Now we introduce a new value object called Discount
. The applyDiscount(Discount discount)
method puts the given discount object on an internal list on the invoice item. When asked for its amount the item uses the list of discounts to calculate the value on demand. The state of the invoice item is never changed. It becomes a value object.
Assertions
Assertions are again a very widely known concept. In some languages, they are supported as a language construct. In others, we can use tests to check them. Assertions define pre- and postconditions of method executions. We can define assertions on method arguments, for example, and also on the internal state of an entity which is especially useful as postconditions.
When we define assertions for our methods, we improve the correctness of our software by disallowing invalid state to propagate. Assertions serve a documentary purpose. Reading a test, that states assertions about the methods of a class, is often more straightforward to understand than the implementation.
Vaughn Vernon discusses assertions in some detail in his book “Implementing Domain-Driven Design” [Ver13] while describing different levels of validation. The first is the attribute level and can be executed when an attribute is set to a new value. The second level is the validation of a whole object. Just because every single attribute of an object is valid doesn’t mean that the whole object is also valid. Therefore we check the validity of different attributes in relation the others. This validation can be invoked internally at the beginning or end of method execution or from the outside. The third level of validation orchestrates assertions of multiple objects on an aggregate, for example.
Wrap Up / Final Thoughts
Supple design is one of my favorite areas in DDD because it addresses multiple concerns. The first is readability: When we write code with great care to these practices, it is a pleasure to read it afterward.
Another one is correctness. When the intention is clear, our interface is more likely to be used correctly. If there are no side-effects bugs are less likely to occur. Validations help to prove the correctness of our code at runtime.
[Eva04] Eric Evans: Domain-Driven Design – Tackling Complexity in the Heart of Software (homepage)
[Ver13] Vaughn Vernon – Implementing Domain-Driven Design (homepage)