Journal of Distributed Software Engineering, Architecture and Design
Iterative Domain Model Design: How to stay autonomous
<div class="cs-rating pd-rating" id="pd_rating_holder_1819065_post_2105"></div>
<p class="wp-block-paragraph">One key question that I hear with Domain Modelling is <em>how do you know if you have your transaction boundary right</em> (so that you build the right aggregate)? </p>
<figure class="wp-block-image size-large"><a href="https://alok-mishra.com/wp-content/uploads/2021/07/screen-shot-2021-07-14-at-10.22.48-pm.png"><img src="https://alok-mishra.com/wp-content/uploads/2021/07/screen-shot-2021-07-14-at-10.22.48-pm.png?w=1024" alt="" class="wp-image-2107" /></a><figcaption>Domain Aggregates and transaction boundaries</figcaption></figure>
<p class="wp-block-paragraph">Hidden in this is another question –<strong> <em>what if we got our domain aggregate wrong?</em></strong><em> </em>We can take this further and ask <em>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?</em> </p>
<p class="wp-block-paragraph">Iterating over a domain model</p>
<p class="wp-block-paragraph">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</p>
<p class="wp-block-paragraph">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. </p>
<figure class="wp-block-image size-large"><a href="https://alok-mishra.com/wp-content/uploads/2021/07/screen-shot-2021-07-14-at-10.42.07-pm.png"><img src="https://alok-mishra.com/wp-content/uploads/2021/07/screen-shot-2021-07-14-at-10.42.07-pm.png?w=1024" alt="" class="wp-image-2109" /></a><figcaption>Customer Aggregate: Initial Iteration</figcaption></figure>
<p class="wp-block-paragraph">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</p>
<figure class="wp-block-image size-large"><a href="https://alok-mishra.com/wp-content/uploads/2021/07/screen-shot-2021-07-14-at-10.42.17-pm.png"><img src="https://alok-mishra.com/wp-content/uploads/2021/07/screen-shot-2021-07-14-at-10.42.17-pm.png?w=1024" alt="" class="wp-image-2115" /></a><figcaption>Version 2 of our domain aggregate: Customer Aggregate now references other aggregates</figcaption></figure>
<p class="wp-block-paragraph">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?</p>
<p class="wp-block-paragraph">The above agility and change question is answered by how we allow consumers to bind to the domain service. How <strong>tightly coupled</strong> are our clients to the domain services and how much do they rely on our internal aggregate design? </p>
<p class="wp-block-paragraph"><strong>Option 1: Direct Binding </strong></p>
<p class="wp-block-paragraph">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</p>
<div class="wp-block-image"><figure class="aligncenter size-large is-resized"><a href="https://alok-mishra.com/wp-content/uploads/2021/07/screen-shot-2021-07-14-at-10.56.01-pm.png"><img src="https://alok-mishra.com/wp-content/uploads/2021/07/screen-shot-2021-07-14-at-10.56.01-pm.png?w=756" alt="" class="wp-image-2111" width="343" height="353" /></a><figcaption>Customer Domain Service with an aggregate</figcaption></figure></div>
<p class="wp-block-paragraph">Using the language of strategic DDD we can describe this relationship in the following manner</p>
<figure class="wp-block-image size-large"><a href="https://alok-mishra.com/wp-content/uploads/2021/07/screen-shot-2021-07-14-at-11.01.23-pm.png"><img src="https://alok-mishra.com/wp-content/uploads/2021/07/screen-shot-2021-07-14-at-11.01.23-pm.png?w=1024" alt="" class="wp-image-2113" /></a><figcaption>Context Map of Customer Management context and relationship with downstream contexts</figcaption></figure>
<p class="wp-block-paragraph">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</p>
<p class="wp-block-paragraph">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 <strong>tight-coupling with a consumer with the veto power</strong></p>
<p class="wp-block-paragraph"><strong>Option 2: Anti-Corruption Layer </strong></p>
<p class="wp-block-paragraph">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)</p>
<p class="wp-block-paragraph">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 </p>
<figure class="wp-block-image size-large"><a href="https://alok-mishra.com/wp-content/uploads/2021/07/screen-shot-2021-07-14-at-11.21.51-pm.png"><img src="https://alok-mishra.com/wp-content/uploads/2021/07/screen-shot-2021-07-14-at-11.21.51-pm.png?w=1024" alt="" class="wp-image-2117" /></a><figcaption>Polysemy</figcaption></figure>
<p class="wp-block-paragraph">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</p>
<figure class="wp-block-image size-large"><a href="https://alok-mishra.com/wp-content/uploads/2021/07/screen-shot-2021-07-14-at-11.24.10-pm.png"><img src="https://alok-mishra.com/wp-content/uploads/2021/07/screen-shot-2021-07-14-at-11.24.10-pm.png?w=1024" alt="" class="wp-image-2119" /></a><figcaption>Anti-corruption layer between an open-host service (API/Events) and consumer </figcaption></figure>
<p class="wp-block-paragraph">and in a component diagram like this </p>
<div class="wp-block-image"><figure class="aligncenter size-large is-resized"><a href="https://alok-mishra.com/wp-content/uploads/2021/07/screen-shot-2021-07-14-at-11.29.09-pm.png"><img src="https://alok-mishra.com/wp-content/uploads/2021/07/screen-shot-2021-07-14-at-11.29.09-pm.png?w=1024" alt="" class="wp-image-2121" width="603" height="475" /></a><figcaption>Using Adapters for anti-corruption to iterate on domain aggregates</figcaption></figure></div>
<p class="wp-block-paragraph"><strong>But this is extra overhead!</strong></p>
<p class="wp-block-paragraph">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</p>
<p class="wp-block-paragraph">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 </p>
<h2 class="wp-block-heading">Summary</h2>
<p class="wp-block-paragraph">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</p>
<p class="wp-block-paragraph">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 </p>
<p class="wp-block-paragraph"></p>
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
Alok brings experience in engineering and architecting distributed software systems from over 20 years across industry and consulting. His posts focus on Systems Integration, API design, Microservices and Event driven systems, Modern Enterprise Architecture and other related topics
View all posts by alokmishra
One of the best explanations, why we should use the “Anti-corruption layer” when your Domain Model is not mature and evolving!! Thanks!!