DDD - Validation for existence of entity in other bounded contexts
Asked Answered
U

2

5

I have a question regarding what best practice is when it comes to verifying existence of entities across bounded contexts. Is this even a valid approach in DDD? BC are supposed to be self-contained deployments essentially (i.e. you should not depend on another BC possibly being unavailable).

I have 2 BC in my project - Ingredients and Recipes. The business sells bulk ingredients but also pre-configured recipes using said ingredients. Now these are separate BC each with their own ingredient entity.

Recipe is an aggregate root that has a child entity of a list of ingredients. Does it make sense to verify that an ingredient exists in the Ingredient BC before adding it to the list of ingredients in the Recipe BC?

Ingredients can only be modified through the ingredient BC where events will be published and the recipe BC will subscribe and update its own ingredients for any changes (i.e. price/name). In order for this to be valid, the ingredient needs to be a valid one. So how do I maintain consistency between these BC? Do I inject a domain service into the recipe BC and validate ingredient existence before adding them? I am using CQRS as well so I could inject the service directly into the handler instead of a factory for creating Recipes (or would that be the right approach to using domain services?).

Sort of lost on this and if this is a valid concern.

Unproductive answered 4/3, 2020 at 21:38 Comment(0)
A
8

Generally speaking, your Recipe should only care about the unique identifier of the Ingredient, and not its details. The details of the Ingredient are not required for the Recipe to be consistent.

I would assume that some action (e.g. a user interacting with a UI) will add ingredients to a recipe. I would also assume that the ingredients that can be added come from a query that only returns valid ingredients. Unless you have reason to be concerned that something/someone is going to subvert this process, you are likely spending time solving a concern that is unlikely to be a real problem.

If this is, in fact, a real concern, then yes, you could validate that the ingredients exists before adding them. However, this would probably be best done near the boundary of the Recipe BC, in a command validator.

A Bounded Context is conceptual--it is (usually) not represented by a single class. I mention this because you ask

Do I inject a domain service into the recipe BC . . . ?

You don't really "inject" into a BC. Again, if you do need this validation, you would likely have a validation class that queries the Ingredient BC via an API or the database to ensure it exists.

the recipe BC will subscribe and update its own ingredients for any changes (i.e. price/name).

This should not be necessary. The recipe has a reference to each of its ingredients, so when you query for a recipe, you query both the list of ingredients and the details of those ingredients. Depending on your setup, this could be a SQL join or something else (there are many different ways this can be done depending on your setup). You should generally avoid caching Ingredient details in the Receipt BC unless you have specific concerns about performance. Caching always adds complexity.

One of the things you will discover as you continue to do CQRS is that a lot of problems you typically think of as "Command Problems" are actually much more easily solved on the query side.

Aborticide answered 4/3, 2020 at 22:26 Comment(11)
In my case, ingredients is in its own BC because thats the primary domain. Recipes is another BC with its own version of ingredients. I can certainly hold just a reference to a list of IDs for ingredients in the recipe BC (and fetch ingr in UI with a query) but I was under the assumption that BC should work by themselves (i.e. even if the ingredient BC is unavailable- recipes will still work and be valid). How do you solve the case where I have a valid list of ingredient IDs but I can't fetch any of them? Is this a problem that is not a concern in DDD where the whole context is self contained?Unproductive
In regards to Recipe - I do have to care about the ingredient because the cost of the recipe is a sum of all ingredient costs + overhead (and a recipe cannot exists without ingredients as it makes no sense) . So I have to ensure that valid ingredients that exist are being passed. Thats why I have an ingredient entity within the recipe BC as this is used to validate the aggregate of recipe + ingredients is correct and persisting it within its own BC. Is there a better way to approach this? I generally read data duplication is not an issue with DDD as its trying to solve domain and not storage.Unproductive
(replying to your 2nd comment). See my last sentence in my answer. Summing the cost of the recipe is a read-side problem! If you're saying that a recipe can't be created if its cost is above $XX, then you likely need to pass the ingredient cost in the command as you create the recipe.Aborticide
(replying to your first comment) BCs can be self-contained but still rely on each other to make the overall system work. If that weren't the case you would need to cache every bit of data from every BC in every other BC to ensure you can always retrieve the data that you need.Aborticide
Thank you! That makes sense as I was approaching it incorrectly. I'll modify my approach with this knowledge in mind. Generally speaking, at what point do you query another BC? If I am using CQRS, do things like querying all the ingredients (i.e. the IDs that exist in my recipe) happen at the application level in the query handler? This might also apply to your point about entity validation (i.e. validate in the command at the application level?). You mentioned at the boundary and this seems like the apt place to place that logic (by using say a domain service to query the other BC API).Unproductive
Some of this is hard to answer without knowing more specifics. For example, if your BCs are all in one service (not separate microservices), it might be completely appropriate to just query the database directly. If you are using microservices, hitting the API from within your query handler sounds totally reasonable as well.Aborticide
For now its still a single application (not microservices) so my plan was to use a domain service specifically tailored for BC communication to do this instead of using a repository. I'm a little hesitant to query the DB directly as all my BC are separated into their own databases. If the eventual goal is to go down the microservices route, I feel using a domain service within my recipe BC to fetch ingredients would fit best for changes later (if I split the BC into their own apps). Please let me know if this is an incorrect approach though.Unproductive
I don't think that's an incorrect approach, and makes it easier to switch to a microservice model later. You do miss out on some benefits of sharing the database, though, like referential integrity and the ease of joining cross-BC data with joins. But as I said, I don't think what you are proposing is incorrect or likely to cause problems.Aborticide
Completely understand. If a shared DB was used in DDD - is it generally a correct thought that DDD does not concern itself with how the data is accessed in the DB? E.g. as you mentioned, I could just query both Recipe + ingredients directly from the DB. Is this not at all a concern because you are potentially crossing BC by bypassing the domain? Especially if data belonging to another BC can be accessed so freely? Is this okay as long as other BC are only accessing cross BC data through a DB for just reads?Unproductive
For SOA/Microservices, a single database per service is a hard rule. For Bounded Contexts it's a bit more grey; I don't believe there is a single correct answer or consensus.Aborticide
"Generally speaking, at what point do you query another BC?" it is possible to establish the rule of "never query another BC". It forces you to think very well about your boundaries and data ownership. If you allow querying another BC, you know that if you make a mistake you can always workaround it by querying another BC. But putting this restriction in your project is a bit extreme. You can at least, try to minimise it as much as possible.Argonaut
B
1

Feels like you BCs boundaries are wrong. Try to find business capabilities they are performing for your business. Don't map 1 to 1 name of the entity with the name of the Bounded Context. Between aggregates you cant guarantee transnational consistency. Even if you check your ingredient, it may be there, but after 1 ms its gone, and you will continue executing your logic, thinking its in there. I don't see why Ingredient should be a separate BC with Recipe. What value does it give.

Banerjee answered 5/3, 2020 at 5:58 Comment(3)
Because ingredients can exist by themselves as they are the primary domain (bulk ingredients to be sold). If an ingredient would be deleted, I would raise a domain event that would update the recipe BC per its business logic. I am simplifying my domain models a bit because ingredients is not just a single entity but for the purpose of the overall domain it is the aggregate root.Unproductive
There is no such Bounded Context Ingredient. Try to find valuable parts of your business. It Could be Ordering, Delivery, Payment. Those 3 things are needed to fulful your business. Cause user should be able to assemble his order, pay for it, and get it. And Recipe and Ingredient entities may exist in ALL of them. In Orders your ingredient is orderItem, in payment BC your ingredient is part of paymentProcess, in Delivery your ingredient is a part of shipment aggregate. Imagine a rule saying you should not select DHL for cheese, cause they don't allow shipping it.Banerjee
So on UI when you add new ingredient it should be spreaded across ALL microservices (Bounded Contexts) which need that data in order to fullfill their business capability.Banerjee

© 2022 - 2024 — McMap. All rights reserved.