I'm working on an order system based on DDD concepts. I'm unsure how to implement the communication between the two bounded contexts Warehouse
and Order
when eventual consistency is not an option. All examples I found advocate for eventual consistency and compare it to Amazon. e.g.: How to resolve Order and Warehouse bounded contexts dependency?
Eventual consistency is possible if items are not limited and can be reordered or are produced after order, but if the item is limited like a ticket for an event, this is not an accaptable solution, especially when many customers try to order the same ticket. Seated tickets exists exactly one time.
If placing the ticket in the cart and blocking it in the warehouse is not immediate consistent many customers could put the same ticket in the shopping cart and then are frustrated when during checkout the ticket is not available anymore. When an event is almost sold out this could happen many times for a customer and the shopping experience would become a race to the check out. This is not an acceptable solution.
It must therefore be ensured that a selected ticket is blocked/reserved immediately before it is placed in the shopping cart. To avoid locking of tickets in stale shopping carts they can be removed after a time out (e.g. 20 mins). Order systems for movie theatre in Germany have a timer that shows how long the ticket stays reserved in the cart.
Our system is implemented as a modular monolith and uses a shared database, therefore I see the following options:
1) Combine Warehouse and Order in one bounded context
This allows transactional immediate consistency. Since there are different requirements for Warehouse
and Order
contexts I would prefer seperated contexts. For other unlimited products immediate consistency would maybe not be an requirement.
2) Database Transaction spanning the two contexts
Since in our current implementation we use a shared database and both contexts run in the same process it would be possible to break DDD recommendations and span a transaction over both bounded contexts. This couples the contexts, but would solve the problem.
3) Use a direct call instead of an integration message
The Warehouse
context exposes a reservation command in the application layer via an interface. The Order
context has a dependency and calls that interface directly. Only if the call is successfull the ticket is added. It is possible that the system crashes in between those two operations. After restart of the application the Order
context needs to check the Warehouse
for reserved tickets that have not been placed in carts, yet. A status flag for each item can limit calls to incomplete processings. It is a tight coupling, but solves the problem.
4) Use messages and wait for answer
I have almost no experience with message buses, so I don't know if this is possible. When the Order
context receives a request to place a ticket in the cart it could send an integration event like ItemRequested
. The code then would need to wait for a response from the Warehouse
context within a certain time span. I visualize it with a tube mail. You go to the counter to request an item. The clerk sends a tube mail and waits for the answer. It is like option 3 using messages and wait for the response.
When the Warehouse
context receives the event it can then react to it, reserve the ticket, and send an integration event like TicketReserved
or TicketNotReserved
. Three cases need to be handled. Sucessfull/failure message arrives before time out, after time out, or not at all.
In case the message arrives in time it is processed by the Order context. If no message arrives in time, the ticket is not placed in the order. A TicketRejected
event is sent by the Order
context. In case the message from the Warehouse
context arrives to late the event is ignored by the Order
context. If the Warehouse
context receives a TicketRejected
event it adds the ticket back to the available tickets in the stock. This keeps the systems decoupled, but increases complexity. It must be ensured that no messages are lost when the system crashes.
Are there other approaches for modular monoliths? I like the decoupling of option 4, but tend to option 3 to reduce complexity at least at first. Is there a structured criteria list that helps to decide which option to go for?