Lead Image © Christos Georghiou, 123RF.com

Lead Image © Christos Georghiou, 123RF.com

The basics of domain-driven design

Bit by Bit

Article from ADMIN 78/2023
By
Domain-driven design addresses many aspects of software development, from the design of entire software landscapes and the relationships between (sub)systems to the design of domain models, patterns, and code.

When Eric Evans wrote his book on domain-driven design (DDD) in 2003 [1], he primarily had enterprise software in mind, wherein project teams develop software for various departments. Since then, a vibrant community has been established around DDD that continues to expand the discipline. As a result, DDD has become widespread in the development of software products.

Domain Models

To build software that solves domain-specific problems, development teams condense their understanding of the domain's structures and rules into a domain model that exists in the minds of developers in the form of diagrams, conversations, text, and code. In DDD, the domain model forms the core of a software system. Of course, this core needs to be surrounded by technology (interfaces, persistence, communication, etc.) for the software to be useful.

Building models was not the brainchild of Evans. He drew on object-oriented analysis and object-oriented design and added two essential concepts that solve problems in the design of large systems. In tactical design (more on this later), Evans describes a pattern language (i.e., coherent patterns) that can be used to design domain models.

The larger the scope of a domain model, the more its inconsistencies become visible. The reasons lie in the domain, because many domain concepts can only be meaningfully defined within a specific context, and forcing them into an enterprise-wide model leads to "god classes" and cyclical dependencies – that is, large, cluttered monoliths that are difficult to evolve. Evans proposed developing context-dependent domain models that unambiguously represent concepts and behavior, which leads to functional modularization of the software, or strategic design.

Strategic DDD

Suppose you had to create software for a movie theater. How would you define a movie ticket? A movie ticket is both a unit of sales (with a price, sales tax, discount options, etc.) and an access token (with validity, validation capability, etc.). To represent all these properties and the complete handling of a move theater ticket in a single model leads to the problems described above, commonly referred to in DDD as the "big ball of mud" [2].

If a domain is too large to be understood and defined as a whole, then it is also too large to model completely in software. In this case, it is better to represent self-contained parts of a domain in separate, smaller models, which allows the software and the development team to grow, limiting the cognitive load for all stakeholders, who no longer need to understand a sprawling overall model, but only manageable models within clearly defined contexts (aka bounded contexts). The goal of strategic design is to design these bounded contexts and their relationships to each other.

To begin, you need to break down the large and complex domain into subdomains. The result of this analysis is the linguistic boundaries within which business solutions can be modeled in a consistent and self-contained manner. In the simplest case, a subdomain becomes a bounded context. Considerations such as team size, technological complexity, strict requirements in terms of user experience, scalability, and more must be taken into account when designing bounded contexts. Designing bounded contexts therefore often means making compromises.

Some subdomains are more critical to business success than others. In DDD, three types of subdomains are distinguished:

  • Core Subdomains (typically simply known as core domains) distinguish a company and are often technically complex. Software for core domains can be developed in-house to provide a competitive advantage.
  • Supporting Subdomains do not represent the technical core of a company, but they are necessary for the core domains' tasks. The technical models of supporting domains are typically specific to a company and rarely to an industry. Software for supporting subdomains can be built by a service provider .
  • Generic Subdomains do not provide a competitive advantage and are not company specific. One classic example is payroll accounting, which is essential for a company, but does not, say, let movie theater operators distinguish themselves from their competitors. As an enterprise, you will want to use off-the-shelf solutions for generic subdomains rather than building the software yourself.

Bounded contexts can be implemented as modules of a monolith or as (micro-)services. They not only draw boundaries between the models, but also (1) between the teams' responsibilities (a team implements at least one bounded context but can implement more than one); (2) between the requirements (e.g., as a separate backlog for each bounded context); (3) in the source code (e.g., as a package tree or namespace, possibly even in the form of separate code repositories in a version control system), and (4) in the data storage.

The last two points in particular are sometimes questioned: Don't they lead to duplicate code and redundant data? In fact, duplication is not a major worry because each bounded context holds only the data relevant to its specific model (Figure 1).

Figure 1: Same name, different model: The MovieTicket class in different contexts of a movie theater.

When you draw boundaries, you have to make sure business processes work across bounded contexts. Bounded contexts therefore need to be integrated, and teams need to consult on the required interfaces. In the DDD universe, this process is known as context mapping, and a number of organizational and technical patterns can be used to facilitate cooperation between teams. For example, a customer-supplier relationship defines a directed dependency between two teams: In Figure 2, Team A (supplier) provides the functionality on which Team B (customer) builds. Team B, as the customer, can typically impose requirements on Team A.

Figure 2: A simple example of a context map for a movie theater.

Context maps like that in Figure 2 visualize bounded contexts, their dependencies, and their organizational and technical integration. Bounded contexts need to be able to perform their tasks as independently as possible (i.e., without calling other bounded contexts). They then are very different from the data-centric services of a service-oriented architecture (SOA). Instead of consisting of bounded contexts such as ticket purchase and admission, an SOA consists of a ticket service, a movie service, and so on. In an SOA, multiple services often need to be called to complete a business task.

Therefore, strategic design is about more than software modularization, technical integration, and data flows. The design of bounded contexts also involves model-level dependencies and relationships between the teams developing the bounded contexts. Team organization in particular has been one of the most common reasons companies have looked at DDD for a number of years. Strategic DDD also provides ideas for one of the most exciting approaches to organizing development teams: team topologies [3] – a model to describe teams and their interactions.

Tactical DDD

What does the architecture of a bounded context look like? When Evans published his book, layered architecture (including a domain layer) was the state of the art. Since then, other architectural styles have emerged that suit DDD even better, most notably hexagonal architecture with a domain core [4].

DDD supports the design of the domain layer or the domain core with a pattern language that defines the building blocks and permitted usage relationships (Table 1). The pattern language does not have to be used for every bounded context, but the more complex the domain expertise, the more worthwhile it becomes.

Table 1

Building Blocks

Module Meaning
Entity Domain-oriented concepts that have a life cycle and identity.
Value objects A domain-oriented concept with value semantics (i.e., without identity); the properties of an entity are expressed as value objects.
Aggregate Related entities and value objects combined to create a consistent whole.
Repository A domain-oriented interface that stores and accesses aggregates (i.e., encapsulates persistence).
Domain service Maps business processes and domain behavior that cannot otherwise be assigned to value objects, entities, and aggregates.

How the building blocks are implemented in the code depends on the programming language and programming paradigm. In the early days of DDD, object-oriented programming with Java dominated publications on tactical design. In the meantime, DDD has arrived in many languages. With the advent of languages such as Kotlin and F#, functional programming has gained widespread appeal in DDD. Many a domain model can be elegantly implemented with functional programming.

Technology openness also applies to the persistence concept. The classic approach is to store the state of an aggregate. Event sourcing [5], on the other hand, stores every change to an aggregate in the form of an event. The current state results from the sum of changes, just as the balance of a bank account results from deposits and withdrawals. DDD has been a major influence in the development of event sourcing. Conversely, event sourcing introduced a concept that entered both domain analysis and tactical design as another design pattern.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy ADMIN Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

comments powered by Disqus