Supple, or elegant and flexible, design is a complement to Deep Modelling. While deep modelling is about digging out domain concepts with the domain experts, and reflecting those concepts in code, Supple Design is about building that code in a way that is easy to understand, modify and extend, in sum: code that is maintainable.
If we want to maintain speed of development as the project grows, we must design code that is a pleasure to work with, code that invites change. Otherwise the code will be a mess, where a small change can aggravate or break something: developers will dread to look at it, let alone work with it.
Eric Evans came up with a set of patterns that can help achieve this supple design.
Intention revealing interfaces
If an interface doesn’t tell the client dev what it does, the client dev will need to go into the interface internal details and implementation, and by doing that, most benefits of encapsulation will be lost.
Name classes and operations to describe their effect and purpose, without reference to the means by which they do what they promise. This relieves the client developer of the need to understand the internals. These names should conform to the UBIQUITOUS LANGUAGE so that team members can quickly infer their meaning. Write a test for a behaviour before creating it, to force your thinking into client developer mode.
Side effect free functions
When many complex elements of a complex design interact, they become extremely difficult to predict. This means that, once again, the client developer must dig deep into the code in order to understand its behaviour and side-effects.
Operations that return a result without having side effects are called functions. Functions, by having no side effects, are very predictable and therefore pose a lower risk of having or originating bugs.
Hence the current hype about Functional Programming and Functional Languages.
Ways to achieve absence of side effects:
- Place as much logic as possible into functions;
- Segregate commands (methods that result in modifications to observable state) into very simple methods that do not return domain information;
- When possible, move complex logic into immutable value objects.
Although the previous patterns help maintain predictable interfaces, there are still situations where state is changed.
The implicit definition of side effects in operations, by means of segregation and naming conventions, is not enough.
A way to explicitly define state changes and side-effects is by using assertions. Some languages provide formal “design by contract” capabilities, where a method or a class has explicit pre and post-conditions.
These assertions verify the state, not the actual technical procedure used to reach the state. This makes it easier to analyse, and we can safely completely replace an implementation by another one because the assertions will fail if the result is not the expected one.
Although not all languages provide this pre and post-conditions constructs, we can still use this approach by means of writing automated tests that verify this assertions.
Conceptual contours relate to bounded contexts and business capabilities. They all represent a context, a boundary around a concept.
The difficult thing about these boundaries is deciding what size they should be, how big or how small.
If we make it too big, in a monolithic approach, we will have very different concepts mixed together, it will be harder to extend and modify, the public interface might become obscure, difficult to understand.
In the other hand, if we break it up in too many small pieces, we might be over engineering, over complicating, forcing the client developer and client code to understand how all these pieces fit together. In the extreme, a concept might be so broken up that the concept meaning gets completely lost.
As we further understand the domain, we will better understand what are the key elements in it. These key elements are the ones we must decompose into highly cohese and decoupled code units (operations, interfaces, classes and AGGREGATES)
The end goal is to have a set of simple interfaces that combine and match with the domain UBIQUITOUS LANGUAGE, without having the burden of options that are irrelevant to the client developer and client code.
A standalone class is an extreme example of low coupling, its a class that is completely self contained and can be studied, understood and tested alone. It needs no other class to perform its function.
Such classes significantly ease the burden of understanding a MODULE. We should make an effort to build and use these classes whenever we can, as a way to simplify the code and have it more maintainable.
Closure of Operations
In mathematics, if we sum two real numbers, the result will also be a real number. So we say that real numbers are “closed under the operation of sum”.
When possible we should define an interface of an operation such that its arguments and return type are the same.
This is specially useful for operations of a VALUE OBJECT, but not often the case for operations of ENTITIES.
This pattern helps simplifying the concept of an operation, making the code obvious, predictable and communicative, which in urn helps in interpretation of code, maintenance and extensibility.
SPECIFICATIONS can be further extended with operations.
Operations like AND, OR and NOT are easy to implement by encapsulating them in another SPECIFICATION that uses two or more other SPECIFICATIONS.
However, that is not always the case. For example, a SUBSUMPTION is an operation that tells us that if a SPECIFICATION evaluates to true, it guarantees that another SPECIFICATION is also true.
All men are mortal B -> C Aristotle is a men A -> B Therefore, Aristotle is mortal A -> C
In other words, a SUBSUMPTION evaluates to true whenever a subset of another more complex SPECIFICATION evaluates to true.
This SUBSUMPTION operation is an example of an operation that can be implemented as a function of a SPECIFICATION object.