Iterative Domain Model Design: How to stay autonomous


One key question that I hear with Domain Modelling is how do you know if you have your transaction boundary right (so that you build the right aggregate)? 

Domain Aggregates and transaction boundaries

Hidden in this is another question – what if we got our domain aggregate wrong? We can take this further and ask In an agile world, given change is constant, if we need to change our domain aggregate then can we change and how does it cost to change? 

Iterating over a domain model

Iterating over a domain aggregate involves testing the transactional boundary of the domain story where this aggregate is used and asking is this too big or too small as a unit of change. Remember, a domain aggregate contains 1 root entity atleast and an entity is something that has an identifier and you manage the lifecycle for

Take for example, a scenario where we are designing the Customer Aggregate. Our initial design may include a large object graph with interactions, cases etc.

Customer Aggregate: Initial Iteration

Over time we realise that our aggregate is large and we can tighten up the model based on transactional boundaries (through more domain storytelling and listening). We come up with a better model like this

Version 2 of our domain aggregate: Customer Aggregate now references other aggregates

So the question is, as we iterate through these versions of the aggregate our domain service will change. How does this impact our service consumers?

The above agility and change question is answered by how we allow consumers to bind to the domain service. How tightly coupled are our clients to the domain services and how much do they rely on our internal aggregate design?

Option 1: Direct Binding

When we expose domain services directly to clients without any abstraction layer (adapters or anti-corruption) then we essentially say that the clients are coupled to the model i.e. they take the aggregate as is and are either happy with the changes or stuck

Customer Domain Service with an aggregate

Using the language of strategic DDD we can describe this relationship in the following manner

Context Map of Customer Management context and relationship with downstream contexts

In the above example, the two downstream contexts are conforming to the model without anti-corruption and obviously impacted by change. Also, as a hypothetical scenario, we added a “customer-supplier” relationship between the Customer Service Management context and the Customer Management context

In this example, we imagine the language of the Service Management relying on customer related things like “Cases” and “Interactions”. Our large aggregate of the Customer which is part of the Customer Management context is now holds external concepts, is bulky and worse – they cannot make any changes because of tight-coupling with a consumer with the veto power

Option 2: Anti-Corruption Layer

One of the hardest lessons I learned from systems integration engineering was to allow room to iterate by using the concept of ports and adapters. This was true even during the SOA days where the model was pre-designed by data architecture teams and deemed to be near-perfect (it rarely was)

With DDD our approach is to build context aligned canonical models and it is okay to see the same word in two different models because they mean two different things. In our example above, the second iteration has “Customer interactions” under customer as a related entry. In the customer management context, we may only need to know interactions as a count (how many). In a deeper context, say Customer Case Management – we may need to know details about the interactions and cases

Polysemy

Sorry, back to our core topic. The key then is to add an anti-corruption layer which is represented in a context-map like this

Anti-corruption layer between an open-host service (API/Events) and consumer

and in a component diagram like this

Using Adapters for anti-corruption to iterate on domain aggregates

But this is extra overhead!

One concern I have heard over the years is that the adapter or experience layer is an extra build, change and network overhead. Given architecture is about balancing choices and the cost of an extra network hop in a well architected design is negligible the question boils down to maintaining extra client adapters and changing them

My experience has been that the extra effort has been worth the client experience, especially for domain services that are not very mature and evolving. The adapters allowed my team to continuously evolve our domain services until we got the model just right and it took a long time to listen, design, validate and model

Summary

Domain models require listening to domain experts, testing your model in the real-world and finessing the model based on the right context and transaction boundary. Domain aggregate design is an iterative process and key to autonomy in domain model is to use an abstraction layer

Consumer adapters a good component model which can provide the mapping for an anti-corruption layer. Adapters and experience layer do extra network hop and are an additional component engineers need to build and manage but they provide heaps of benefit in flexibility for changing your domain model, especially when you are not entirely sure

1 Comment

  1. rajeshmku says:

    One of the best explanations, why we should use the “Anti-corruption layer” when your Domain Model is not mature and evolving!! Thanks!!

    Liked by 1 person

Leave a Reply to rajeshmku Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s