Reputation: 771
Let’s say I have Product model. I have Catalog Context, Warehouse Context, Shipping Context, Sales Context. My Product entity is scattered into these Context. Catalog, Warehouse,Sales and Shipping has its own Product. Id property is shared. The Sales bounded context cares about price, product name, description. The Warehouse bounded context cares about sizes, position, quantity, quality and so and so for.
Now I want a sales person to add a new product to the system. From UI, the sales person enters all the necessary details for the product containing, Name, Description, Quantity, Price etc and clicks submit. So my system should create product object for each context using these request details. Sales would create its own product and Warehouse would create its own product and so on and so for.
Once request received to my controller, I would Create CommandObject using request details. (Commands for create product in each context).
Controller
Guid id = Guid.NewGuid();
var salelsCreateProductComand = new salesContext.application.product.CreateproductCommand(id, …);
var warehouseCreateProductComand = new warehouseContext.application.product.CreateproductCommand(id, …);
var shippingCreateProductComand = new shippingContext.application.product.CreateproductCommand(id, …);
var salesContextTask = _sender.Publish(salelsCreateProductComand);
var warehouseContextTask = _sender.Publish(warehouseCreateProductComand);
var shippingContextTask = _sender.Publish(shippingCreateProductComand);
await Task.WhenAll(salesContextTask, warehouseContextTask, shippingContextTask);
I’ll generate Guid from controller and set it as the product id.(Then each context will share the same id) Then publish the commands to MediatR. Then each context will take the command and related handler will take care of it.
But one context might fails to create its product object for some reason. May be database failure or validation failure. So we need to rollback the project object from other models as well.
How can we handle this kind of a scenario?
Really appreciate if you can explain any suggestions/improvements/mistakes/bad practices in my solution.
PS:
I feel bad about generating the product id from the controller.
Upvotes: 0
Views: 109
Reputation: 711
This problem calls for the SAGA pattern. In this situation, you have multiple best practices:
To understand what SAGA is about, it's better to think asynchronously. Each of the bounded contexts must handle the ProductCreatedEvent. Each of them will try to save the product on their database and emit a message of success or failure to signal other bounded contexts of their result. And other bounded contexts can decide what to do with the received message.
There are two types of SAGA to design such messaging and communications between bounded contexts: choreography-based and orchestration-based.
In choreography-based SAGA, there is no central module to orchestrate the communication between bounded contexts. They communicate via a message broker with a pub-sub style. You can either design the events sequentially (events emit one after another when they are handled, they are sequentially coupled, and each of the bounded contexts knows the next step event) or design them to be ignorant of other bounded contexts (simple to design but complex issues arise like out-of-order messages).
In orchestration-based SAGA, a central module orchestrates the steps of processing a request between multiple bounded contexts. This module persists a state manager object (like a workflow object) to track the state of each bounded context.
Using only the SAGA pattern won't solve the consistency issue (because of the possibility of messaging failures). It's better to use it with the Outbox pattern (which ensures that messages will be sent eventually when an entity successfully commits to the database).
Upvotes: 1