Reflect after Reading - Domain Driven Design

27 August 2015

If you knew nothing about it, you could be forgiven for thinking that Domain Driven Design: Tackling Complexity in the Heart Of Software by Eric Evans was selling a Methodology. The kind that is described (and derided) in Peopleware as “a general systems theory of how a whole class of thought-intensive work ought to be conducted”. It ticks the boxes: it comes in a big fat book, you can take courses in it, and it claims to solve your software development woes once and for all…

…Or does it? Domain Driven Design ends by quoting Christopher Alexander, the architect-philosopher who also influenced the Peopleware authors. Alexander argues that successful building projects are a result of “piecemeal growth” and “organic order” rather than the logical conclusion of a “master plan”. Just like the Peopleware authors, Evans believes this conclusion is also applicable to the field of software engineering. In Domain Driven Design Evans takes great care to avoid creating the illusion that DDD is a golden recipe for success (or even that it is a recipe at all). Instead, the book is a very subtle and detailed exposition of a certain “attitude” that, when used to guide the development process, can lead to the creation of enterprise scale software that exhibits the “organic order” Alexander sought in his architectural projects.

Domain Driven Design is a deep reflection on the process of software development, and the conclusions it reaches can be characterized as a philosophy: a way of thinking about and practicing software engineering. These conclusions are structured into “analysis patterns” and illustrated with detailed examples from Evans’ career as a software engineer. DDD is 10 years old and still relevant (amazing for the software industry!), despite the fact it emerged from a world when object oriented programming was all the rage, Extreme Programming didn’t feel like a gimmick and the answer to every data persistence question was “relational database”. It is testimony to the depth of the insights in DDD that in a world dominated by microservices, functional programming and NoSQL it continues to thrive and help developers deliver complex software projects.

The philosophy

The core concept in the DDD philosophy is the domain model. A model is defined as “a system of abstractions that describes selected aspects of a domain and can be used to solve problems related to that domain”. But what concrete form does this model take? How is this “system of abstractions” expressed? Is it contained in a modeling tool? Is it described in documentation? Is it the code itself? While all of these contribute, the most important expression of the model in DDD is the UBIQUITOUS LANGUAGE. This is the language used by the development team when discussing their ideas about the software. It is the language that is employed when communicating with domain experts. It provides the vocabulary for naming classes and methods in the software itself. In DDD, the model does not have a canonical expression, but instead permeates every aspect of the software development process.

Using and working with the model takes discipline. Developers have to pay close attention to their use of language, and extract the model from conversations with domain experts. The ultimate goal of a team applying DDD is to arrive at what Evans refers to as a DEEP MODEL, expressed in code through the application of SUPPLE DESIGN. A deep model can be said to “carve nature at the joints” (in Plato’s words), or as Evans puts it, follows the CONCEPTUAL CONTOURS of the domain. A deep model provides a representation of the domain that enables rich applications to be built with relative ease. It allows new functionality to slot in beside existing functionality. Changing requirements can be accommodated without a complete rethink of the application structure. In keeping with the teachings of Agile and Extreme Programming, a deep model is arrived at through numerous iterations and refactorings. Furthermore, Evans describes how a process of “distillation” allows the development team to strip the model of superfluous elements and reduce it to its core. During the development process, the team must keep a close tab on the evolution of the ubiquitous language and ensure that any changes are reflected in the code (and documentation). Furthermore, any difficulty or awkwardness experienced during development should be taken as a hint that the model needs to be adjusted. Through this process of constant refinement, a team can produce a deep model that provides real value to the business.

The patterns

In order to help developers arrive at a deep model, Evans suggests several patterns that can be used to structure the software. These patterns are abstract enough to be relevant to a variety of domains, yet precise enough to be useful. The patterns can broadly be divided into two categories, depending on the level of abstraction at which they apply. The first category contains patterns that are applicable at the level of objects and classes. In this sense they are similar to the “gang of four” design patterns. The patterns in the second category form the basis for what Evans refers to as STRATEGIC DESIGN. These are applicable at a much higher level of abstraction and are intended to help structure software at the level of an entire enterprise. Although this sounds worryingly over-ambitious, one of the fundamental principles of strategic design is that all-encompassing models are impossible to create. For this reason, structure at the level of the enterprise relies on carving up the software into BOUNDED CONTEXTS and defining clearly how these contexts interact. I have gathered a small selection of the most memorable patterns. In the book all of these are illustrated by detailed examples.

ENTITY An element of the model that has long lasting identity, for instance a Customer in a typical CRM tool. Throughout its lifecycle, an entity may be represented in memory by different objects at different times. Special care must be taken when trying to identify entities in terms of their attributes. For instance, the “name” field is not a good way to identify a customer (many customers are likely to have the same name).

AGGREGATE A class representing an entity will often participate in relationships with other classes. These networks of classes maintain application state that must often respect certain invariants: an update to a certain part of the model entails an update to another part as well. In order to make these dependencies manageable, Evans introduces the AGGREGATE pattern. An aggregate is a collection of interrelated classes whose state is governed by certain invariants. All updates to the aggregate must go through a certain special class referred to as the AGGREGATE ROOT. This class is responsible for orchestrating state updates in such a way that invariants are never violated. Because all updates must go through the aggregate root, the state of the aggregate is shielded from incorrect modifications by possible clients.

REPOSITORY A pattern that addresses the difficulties of persisting data. A repository is a class that offers an API for finding and querying objects that may have been persisted to a database. The main objective of the repository pattern is to keep the domain layer insulated from the technical infrastructure needed to access the database. A repository is not an object-relational mapper, although it may be implemented using one. When using repositories, Evans offers the following words of caution: “client code ignores REPOSITORY implementation; developers do not”. The take-away message is that while hiding database access code behind an INTENTION REVEALING INTERFACE (another DDD pattern) makes for cleaner code, it can have dire performance consequences if developers are not aware of how the repository queries are implemented. The tension between performance and conceptual clarity of the code is what makes persisting data such a fascinating and challenging topic.

VALUE OBJECT A class that is the collection of its attributes. Two value objects are equal when all their attributes are equal (in the Scala programming language case classes are the perfect tool for representing value objects). These combine particularly well with the next pattern.

SIDE EFFECT FREE FUNCTION A function that computes its result without performing any side effects. In the functional programming community this type of function is also called a “pure function”. Pure functions are desirable because they are easy to reason about. The result they produce is a function (in the mathematical sense) of their input. Using value objects as the parameters and return value of a side effect free function makes for clean and elegant code. Side effect free functions are also easy to unit test.

BOUNDED CONTEXT The core pattern in terms of which the other strategic design patterns are expressed. A bounded context is a coherent model that stands on its own and is isolated from other models. When creating a very large application it will be impossible to capture all aspects of the domain in the same model. Nevertheless, different parts of the application (different contexts) will overlap in their coverage of the domain. Although good software engineering practice would seem to point towards maximal model re-use, Evans shows that careless model sharing can lead to great difficulties. For this reason, drawing a CONTEXT MAP and thinking clearly about the relationships between the models used by different parts of the application is crucial to creating software that can scale conceptually to cover the domain of a large enterprise.

The future (the present)

It is remarkable to note that DDD has managed to stay in the tech industry’s consciousness for over ten years. This is a sign that the insight it offers is of significant value. In his interview with software engineering radio, Evans reflects on the evolution of DDD, and the applicability of its concepts to today’s tech landscape. In particular, he discusses the following subjects: microservices, event sourcing/CQRS (Command Query Responsibility Segregation), functional programming, the actor model and NoSQL.

MICROSERVICES Evans explains that in the early days of DDD there was no technical support for implementing bounded contexts. It was down to the discipline of development teams to ensure that their models were kept clean and had clear relationships with the models of other teams. Microservices solve this problem by providing clear boundaries for the validity of a model. Microservices are meant to be standalone and sealed off from their environment, communicating with other services through a well defined interface. Interestingly, Evans equates a bounded context with a cluster of microservices rather than with a single one. The microservices within such a cluster speak the same language and therefore can communicate effortlessly with each other. Special care is needed to provide appropriate translation when a microservice from one bounded context calls a microservice from a different bounded context. Structuring a code base into microservices forces developers to think about the boundaries within which their model applies.

EVENT SOURCING/CQRS Event sourcing and CQRS are two architectural styles that were developed by DDD practitioners searching for better modeling strategies. Precisely because they were focused on finding the best way to express the core concepts of the domain in their software, these practitioners were led beyond the patterns that Evans presents in his book to discover ones of their own. The key insight that inspired these approaches was that instead of focusing on state, a domain model should be structured around state change. State is then reconstructed from state change by “rolling up” a sequence of changes. State changes are immutable, which means they are better suited for supporting a scalable distributed system. They also match well with the fundamental principles of functional programming…

FUNCTIONAL PROGRAMMING Immutability and pure functions are two fundamental principles of functional programming. We have already seen that DDD can be made to work with immutable domain objects by applying the modeling techniques of event sourcing. As for pure functions, we saw in the previous section that they are already an important pattern within DDD.

ACTOR MODEL The actor model is a way of structuring programs to make them easy to execute in parallel over distributed computing resources. Akka is a popular implementation of the actor model in the Scala programming language. In this context, the aggregate pattern gains a new lease of life. The principle motivation behind the aggregate pattern was to provide a mechanism to ensure that application state did not become corrupted by incomplete updates. In the context of the actor model, an aggregate can be implemented by a group of actors managed by one actor which represents the aggregate root. This root actor is responsible for receiving any messages from the application and then updating its child actors appropriately. Because actors are completely shielded from each other and cannot access each other’s state, the actor model is a very appropriate technical basis for implementing the aggregate pattern.

NOSQL Although Evans concedes that the catalyst for the emergence of NoSQL databases was improved performance, he explains that their most important contribution is actually the introduction of new modeling techniques to the database realm. He suggests that NoSQL should actually be read as an acronym for “Not Only SQL”. At the time DDD was written, the only way to persist data was in a relational database. Although this model is applicable in many cases, it is not always a good fit. The emergence of graph databases, column databases, key-value stores etc… enables developers to choose a persistence technology that follows the conceptual contours of the domain in which they are working.


References