Being Successful With Domain-Driven Design: Minimal Complexity, Part 1

Published on Friday, May 19, 2023

Concepts, Context, and Boundaries. Abstract Thought The Domain-Driven Design book (the "Blue Book") includes "Tackling complexity at the heart of software" in the title. While "complexity" can be subjective, the takeaway is that Domain-Driven Design intends to address complex software systems. The principles and practices in Domain-Driven Design have their complexities, so for Domain-Driven Design to add value, it needs to address existing/expected complexity and attempt to be net-positive for simplicity.

Interestingly, the title of the Blue Book alludes to questions about what complexity is at the heart of software. Subjectively subjective, but we can look to the intent of some of the patterns and practices to deduce some types of complexity that Domain-Driven Design adequately addresses in the design of software systems.

For this series, I'll tranche away some of the patterns as essential complexity (essential complexity of both Domain-Driven design and the design of almost all software design): Entities, Value Objects, Modules, Layered Architecture, Factories, and Repositories. Any modular software system must deal with identity, value, creation, and storage. There's nothing new about layered architecture, but Domain-Driven Design does detail the isolation of specific responsibilities (like Domain and Infrastructure) that I'll cover. Practices like Side-Effect Free Functions, Standalone Classes, Intention-Revealing Interfaces, Continuous Integration, Assertions, and Declarative Style are aspects of long-championed techniques like cohesion, loose-coupling, naming standards, or functional programming (in my opinion).

None of what I've tranched off are unimportant, but they have been tried and true before Domain-Driven Design, and I want to focus on added value in Domain-Driven Design. To that end, I'll focus on Ubiquitous Language, Bounded Context, Context Map, Aggregates, Services, Domain Layer, Generic Subdomains, Segregated Core, Anti-Corruption Layer, and Core Domain; and touch on Evolving Order.

Clean Concepts, Contexts, and Boundaries

If I had to distill the intent of Domain-Driven Design to a single statement, it might be "be explicit." Or, more explicitly: "Be explicit with boundaries." Software systems are not the only source (or victim) of complexity. There's a whole science devoted to it: Complex Adaptive Systems. Not to oversimplify Complex Adaptive Systems, but systems with sufficient complexity are inherently unpredictable and exhibit emergent behavior, among other things. Meaning that complex systems will do what they're going to do, and we can only sometimes predict what they will do. Sometimes that emergent behavior is beneficial; sometimes, it isn't. To make systems more predictable (and get the benefits that provides), we have to reduce complexity. The complexity of complex systems arises from the number of dependencies, relationships, and interactions. Each unbounded interconnection increases complexity exponentially.

Complex adaptive systems theory is why explicit boundaries are a major aspect of how Domain-Driven Design combats complexity in software to produce more reliable and robust systems. Explicitness is important here; we're not looking for any-old boundaries. We're looking to constrain and isolate areas of the system based on purpose, meaning, and intent. We could chalk this up as simply an exercise in cohesion, but Domain-Driven Design focuses on getting to and clarifying that purpose, meaning, and intent.

Explicitness starts with unambiguous concepts, descriptions, and terms. If people aren't communicating the same concept things aren't going to get simpler. I speak about Naming Things, and part of what makes that difficult, I've decided, is language (or English). Stemming from human nature, we try to classify an ever-increasing set of concepts with a finite set of words, syntax, and semantics. The first step to explicit boundaries is the agreement on what they are: agreement on the concepts and to which explicit context they apply—the Ubiquitous Language.

The value of Ubiquitous Language isn't just that there is an agreed-upon vocabulary. The added value to a Ubiquitous Language is what it accounts for. The Ubiquitous Language recognizes classifications of concepts, classifications common to most software systems. Classifications that the Ubiquitous Language fosters and isolates: individuals, invariants and consistency rules, operations, processes, collaborations, commands, events, views, and values/properties. By "individuals," I don't just mean people, but anything that exhibits individuality (aka "entity.")

Imagine an amorphous "loan" concept in the financial industry. People apply for loans, obtain loans, and pay back loans. Getting a loan involves evaluating personal information (credit rating, assets, liabilities, etc.). Paying back a loan consists of a term, an interest rate, a payment schedule, etc. Credit rating, assets, liabilities, term, interest rate, and payment schedule are six interconnected concepts. With six concepts (with each having five interconnections to the others), there are 30 interconnections. Or 30 complexity points. But, if we think of these six concepts as two different semi-independent contexts: "loan application" and "loan servicing," we end up with two contexts (and one interconnection) with three concepts (or three interconnections) totaling six interconnections. We've gone from 30 complexity points to 6 simply by defining the actual contexts better. In other words, we're being more explicit.

Explicitness like this--delineating elements of a set into two groups connected by a specific relationship--is creating a boundary between two contexts. This is a very simple example of Bounded Context. As the name implies, Bounded Context is an explicit contextual boundary: where one context ends and another begins. This is a domain's macro level, recognizing that Loan Servicing can only happen after Loan Application is successful. This particular boundary is based on a temporal or procedural boundary. Phases or steps are a good way of organizing domains into bounded contexts.

In these two contexts, the word "loan" exists in both. The word "loan" is used in the application context as well as in the servicing context. But, in the application context, the meaning is really "loan application," and in the servicing context, it really means "serviced loan". Understanding that servicing depends on an approval event in a loan application phase (or activity) allows us to realize an explicit boundary. Sometimes it's as easy as this, but often it's not. There are other ways of teasing out boundaries (or contexts), almost always involving vocabulary elements.

Sometimes you've got overloaded terms like "loan"; sometimes, you have different terms like different rules. Different rules are typically applied in different scenarios or involve different parameters. Different rules offer a window into recognizing different contexts with a boundary in-between. You may recognize concepts like this (events, operations, rules/invariants) from the larger list I mentioned above. A Ubiquitous Language can also account for individuals and entities, commands (often related to an activity), views (reports, screens, results), collaborations, and attributes or properties attributed to individuals and entities. Additionally, attributes or properties can be involved in criteria, and categories or subtypes may group individuals and entities.

Working towards a Ubiquitous Language is working towards concepts more independent from each other. Independent concepts are themselves individual contexts. Any defined concept has a defined context with understandable boundaries. Keeping the complexity of one context bound from others keeps the essential complexity within that context and reduces the accidental complexity that arises from blended contexts.

Domain-Driven Design adds value when you have a minimal complexity, when a subject matter has multiple terms per classification. Terms can be classified as entities, processes, phases, events, rules, views, etc. The focus of this post was a level of complexity where boundaries are recognizable in the nuances of the vocabulary. In future posts, I'll dig deeper into different the subject matter (or domain) classifications, how you can isolate the complexities of each, and the parts of Domain-Driven Design that apply.

comments powered by Disqus