1

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 Warehousecontext 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?

M. Koch
  • 525
  • 4
  • 20

1 Answers1

1

Nice writing!

First of all, in my opinion, strong consistency is the way to go for the majority of systems.

Since the database is shared, there is no fundamentally wrong with using transactions to handle the correctness. If the tool is already there, I would use it.

Saying that, I would code/refactor the code to make sure I can eventually split the database for each service. That doesn't have to be a zero-effort work, as splitting databases involves migrations and other complex steps; so replacing transactions with something else is just another task.

Having each service being responsible for their own context is a nice feature - it does help to simplify the overall architecture. In that case, even the database is shared, we will pretend it is not, and we will expose all operations via APIs (as you described).

When we have these multiple services and we have a cross services transactions, two approaches may help to manage transactions: a) two/tree phase commits and b) Saga pattern.

I would not recommend two/three phase commits as they add extra complexity to the system, mostly around handling failover scenarios.

I would recommend to look into Saga - which is more or less is just a chain of independent transactions.

Summary:

  • I would start with local transactions
  • I would keep the door open for migration to Saga pattern
AndrewR
  • 1,252
  • 8
  • 7
  • Thanks for your input. My main problem is that I need immediate consistency during the processing of the initial request. I can't return the response for the addItem request until the tickets is reserved in the Warehouse context. – M. Koch Apr 01 '23 at 10:03
  • Saga perfectly fits into your needs. Saga is implemented via a dedicated orchestrator or via choreography (https://learn.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga). In your case, I would use orchestrator to be that thing to process the initial request (maybe via some api layer). Hence, the request won't return a result till tickets are reserved. – AndrewR Apr 01 '23 at 17:02
  • In my head I wrongly thought of Sagas being asynchroneous. You wrote you would start with local transactions. Do you mean to keep both parts in one context or span the transaction with EF Core across the two BCs? The system is planned to be extended for many options. Therefore I would like to keep Warehouse and Order in seperate bounded contexts. Option 1 would make it much easier, but the rewrite later would be enormeous. – M. Koch Apr 09 '23 at 16:58