Search code examples
c#domain-driven-designvalue-objectsbounded-contexts

DDD - How to use Shared Kernel with multiple bounded contexts?


Shared Kernel describes the relationship between TWO bounded contexts. I haven't found any helpful information how to efficently handle shared objects between multiple bounded contexts.

We use value objects instead of primitive types. Entity Ids are also typed and implemented as value objects. So there are types like ProductId, ServiceId, OrderId, PersonId, ClothingSize, ServiceComment, MusicStyle, DanceStyle, FoodRestrictions, Money, and many many more.

I'm struggling how to implement those shared objects. It is straight forward if you only consider two bounded contexts. A shared kernel can contain the objects that are used in both contexts.

If there are many bounded contexts and a separate shared kernel would be used between each pair of contexts the number of shared kernels will explode by n*(n-1). Value objects that are used in multiple contexts will be duplicated again and the shared context will loose its benefit.

Each value object also requires a value converter for EF Core and ressource files for the translation of the field names and error messages.

One solution would be to use one large shared kernel. All objects that are used in at least two bounded contexts end up here. The disadvantage is that if a new bounded context is added that uses a value object that was not used by another context before it has to be moved to the shared kernel (and maybe removed again, if it is not shared anymore). For example we had a clothing size value object for the warehouse context and then needed this for the service contracts context, because artists got a festival tshirt and the size was stated in the contract.

There are technical base classes like for value object, entity, aggregates, error, result, etc. that are used in every context and without doubt belong to a base shared kernel.

Many of the shared value objects instead are not used in all bounded contexts, but rather in just some.

We use C# and .Net Core. We are a very small team and use a single SQL database. By experience this is sufficent for our load. In the future the system might be expanded, but it is not expected that a microservice solution wll be necassary, but it can't be ruled out.

For each different layer (API, application, domain, infrastructure/persistence) we use a separate project. The dependencies between the projects restrict the access between the layers.

I have the idea to create a shared layer in each bounded context. For example I would create a shared domain layer for the warehouse context that contains the value objects that other contexts need and therefore should be shared. Then only the contexts that require those shared objects need a dependency to this project.

Since EF Core converters are in the infrastructure/persistence layer I would need a shared infrastructure in each context, too, but since this is a technical aspect, I think it would also be Ok that all converters go into the base shared kernel.

This architecture would very clearly define the dependencies to the shared objects in each context. On the other hand this might overcomplicate the whole system. I wonder if this extra complexity has enough benefit or if it would just be easier to have only one shared kernel between all bounded contexts.

I need to restructure the code now. This is my first DDD project and I don't want to do a lot of work to find out after two weeks that the choosen option doesn't work.

How did successful projects tackle this problem? Since I lack experience I would like to know if my idea has been successfully implemented and what problems each solution bring, so I can make an educated decision for our case.

Thanks for any help!


Solution

  • My approach to the challenge you describe is to make common value object and typed ids part of the 'Global Language' of the solution.

    To this end, I deploy all of my shared value objects and typed ids in that single project 'GlobalLanguage'. Similarly, a shared EF converters project.

    The key thing with a Shared Kernel is not so much how many projects use that shared kernel but more about recognising that anything you put in there is no longer 'bounded' and needs to be reviewed against the needs of all domains that depend on it should you wish to change an item in that Shared Kernel.