The Most Common Domain-Driven Design Mistake

Mistaking Central Concepts for Bounded Contexts

Hany Elemary
navalia

--

I have made countless architectural mistakes while working in technology. But I’ve found that mistakes made with Domain-Driven Design (DDD) are specially unforgiving. Wrong abstractions in DDD have more disruptive implications than in other design approaches.

In this article, I share the most costly design mistake in DDD; A common mistake that leads to a prevalence of monolithic and tightly coupled systems.

Introduction

I’ve come across plenty of bloated and fragile Customer APIs in different organizations. And the proposed solution to this fragility would end up breaking the Customer API into smaller, purpose-driven services when it inevitably became too cumbersome.

Since businesses exist to serve customers, treating Customer as an API is too vague and rather lazy. In fact, the lack of specificity is what leads to this bloat and fragility, which you can easily see in other examples such as Order API, Product API, Account API, etc.

What we intend to model in most cases is the business processes surrounding these concepts, not the concepts themselves. And that leads us to our common mistake.

Mistaking Central Concepts for Bounded Contexts

A “Central Concept” is a key idea in a particular industry, like:

  • Account in banking.
  • Policy in Insurance.
  • Product in Supply Chain Management.
  • Reservation in Airlines.
  • Order in eCommerce.
  • Customer in eCommerce.

A “Bounded Context” is an area of the business where certain processes and rules apply. It’s where the Central Concept shows up repeatedly under different set of rules that make sense for each Bounded Context, such as:

  • Account in banking looks different to Loan Origination vs. Billing vs. Debt Collections vs. Marketing & Communications.
  • Policy in Insurance looks different to Underwriting vs. Claims vs. Inspections.
  • Product in Supply Chain Management looks different to Planning vs. Sourcing vs. Inventory Management vs. Delivery.
  • Reservation in Airlines looks different to Booking vs. Operations vs. Cargo Management vs. Loyalty.
  • Order in eCommerce looks different to Purchasing vs. Supply Chain vs. Fulfillment vs. Customer Support.
  • Customer in eCommerce looks different to Advertising vs. Ordering vs. Shipping.

This is where the problem lies. Because of their complexity and how often they show up, Central Concepts in DDD trip us up more than we realize. They deceive us into treating them as genuine Bounded Contexts.

Let’s focus on the banking industry where I first experienced this mistake. We decided to build an Account API — a seemingly reasonable idea at first glance. However, an Account should not be a Bounded Context or an API. I’ll outline the reasons from two different perspectives; a theoretical perspective and a practical one.

The theoretical perspective.

First, claiming a single view of Accounts goes against the core principles of DDD as it results in an inflexible, bloated Domain Model.

Different Bounded Contexts perceive an Account through a unique lens, and in some cases, an Account may even be called something else across the business (Ubiquitous Language).

To illustrate, here’s a list of Bounded Contexts in our example with the Account information they focus on:

  • Loan Origination focuses on background checks, risk assessments, and Account terms such as interest rates and credit limits.
  • Billing is concerned with timely payments and statement generation for the Account.
  • Marketing and Communications focus on customer communication preferences and payment reminders for Accounts.
  • Debt Collection assists customers in hardships by lowering interest rates or offering alternate payment plans on Accounts. In some unfortunate cases, they may sell Accounts to debt collection agencies — the ones that harass customers and go after their assets.

Second, DDD is about solving complex business problems where the software model should reflect the business processes. Managing Account preferences is a business process. Debt collection is a business process. But an Account is not. It’s a concept that exists within different business processes.

The practical perspective.

Treating the Account as a Bounded Context leads to negative cascading consequences.

Unnecessary Coupling

The first is that the Accounts Bounded Context becomes a ‘God Domain,’ taking on more responsibility than it should. As a result, this creates tight coupling with other Bounded Contexts.

Team Friction

This coupling negatively impacts organizing teams around the problem. All teams must interact with Accounts to make changes, which increases dependencies and friction. This leads to one of the following undesirable side effects:

  • The Accounts team becomes overloaded with feature requests from other teams, causing delay as the number of requests increases.
  • The Accounts team enables other teams to submit pull requests for their changes. This approach still causes delay as the Accounts team reviews a large volume of requests.
  • The Accounts team size increases to match the workload but team members start to have merge conflicts more often.

Sadly, all the approaches outlined above hinder team autonomy and software release predictability. As a result, organizations become constrained when executing on many ideas at the same time.

Single Point of Failure and Resiliency Issues

With other Bounded Contexts requesting info from (or propagating info to) Accounts, cross-domain interactions become chatty. This leads to performance and resilience issues.

With Accounts as the source of truth, it also becomes a single point of failure for the entire system. For example, if a bug in the Account origination code causes a memory leak, it could disrupt services in unrelated areas, like payment scheduling. This means not only is account creation affected, but also another key business function — processing payments — is impacted.

Avoiding the Trap

Decomposition & Encapsulation

As I evaluate my experience, I realize that a big chunk of it went into the decomposition and encapsulation of systems.

Decomposition is about breaking the problem into smaller chunks. These chunks often resemble business rules that have to be scoped. And that’s where encapsulation comes in.

Encapsulation is about defining the boundary of the business rules while protecting them from leaking out to other areas. It defines how other systems access these rules and interact with them.

DDD is not different. With the understanding we now have that an Account has many representations under different circumstances, we should evaluate if it can be decomposed into other Bounded Contexts.

The next step is to think about how to encapsulate the business rules so they’re scoped and protected in the Bounded Context. In other words, we should think about how precisely do we represent the Central Concept, Account.

That’s where Aggregates come in. Aggregates are units of encapsulation for our Account. They’re responsible for upholding the integrity of the data according to the business processes. They can define access rules via fit-for-purpose APIs within their own Bounded Contexts.

Here is an example of how we may decompose Accounts into Aggregates within different Bounded Contexts:

Dissolving the Accounts Bounded Context into Aggregates (CollectionAccount, AccountPreferences, AccountTerms and AccountPayments)

For Debt Collection, the concept of Account is called CollectionAccount to denote its different status from good-standing Accounts. If you need to grant a specific PaymentPlan (let’s say without interest rates), you have to go through the CollectionAccount to enable that change.

Under Billing, AccountPayments is the Central Concept (Aggregate). Enabling customers to make payments, view statements and payment history are examples of operations specific to that Bounded Context.

From an implementation perspective, you should expect the CollectionAccount in Debt Collection to have a different schema and set of behaviors from the AccountPayments in Billing. That is totally normal and, in fact, encouraged by DDD principles.

Closing thoughts

Domain-Driven Design is about modeling complex business processes and rules. A common mistake in DDD is the incorrect modeling of Central Concepts — such as “Accounts” in financial services, “Customer” in eCommerce, and “Policy” in insurance — as Bounded Contexts or APIs. This mistake is quite costly to reverse, especially when using Domain-Driven Design.

To avoid this trap, the key is to tie the design back to the business process being modeled. These Central Concepts do play a critical role in Domain-Driven Design. But their role is as maintainers and controllers (aka: Aggregates) of the overall consistency of the Bounded Context rather than being the Bounded Context themselves.

Acknowledgments

Big thanks to Smitha Ajay, Luke Belliveau, Elliott Branecky, Arthur Cohen, John Johnston (JJ), Sharan Karanth, Megan Lusher, Matthew Moore, Cliff Morehead, Lav Pathak, Moises Prestes, and Jason Smith for reading drafts of this article and providing valuable feedback.

--

--

Hany Elemary
navalia

Technology Leader. High Performing Teams Enabler. Author & Speaker.