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)?
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.
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
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
Using the language of strategic DDD we can describe this relationship in the following manner
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
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
and in a component diagram like this
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
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