Architecture – How to handle transactional operations in an event-driven architecture

Architectureevent-sourcingeventual-consistencymicroservicestransaction

I'm trying to flesh out an eCommerce system using microservices (.NET Core and Kubernetes), event sourcing (Kafka), and CQRS. The particular use case I've been thinking about is as follows.

There is an inventory microservice designed to use CQRS. Updates are fed into Kafka as events, which the inventory microservice consumes to update its materialized view, and reads are executed directly against the materialized view. The issue I'm trying to work through is how to handle orders. In my current design, orders are created in an order microservice which then emits events which the inventory microservice would consume and deduct the inventory that was part of the order. However, there's a race condition here. It's possible for another buyer to buy the same products before the inventory from the previous order can be deducted.

How does one handle this type of transactional operation that spans microservices? I've read that one should verify stock against the stream instead of the materialized view (as the stream is the actual source of truth), but I'm a little fuzzy on how that would be practical (as the stream might be huge). If I'm going to do that then why even have the materialized view if I can't trust it?

Best Answer

In my experience, most questions about transactionability and microservices are caused by the following two reasons:

  1. The transactional data is placed in different microservices: This is wrong by definition. Data that should be modified transactionaly belongs to the same service. Grouping the right data in the right service is very tricky and you need to put a lot of thought into it. If you just create microservices based on entities (Orders, Users, Inventory, Products, etc), then you will find the same problem over and over, plus you'll end up with N services depending on N other services to perform their purpose. Therefore: put all data that changes transactionally together.

  2. The operation is not transactional in the first place: You are trying to solve with technology something that should be solved by business rules. This is your scenario in my opinion. Orders and Inventory are not transactional. You obviously don't want people buying products out of stock, but you have to accept that there will be always a slight chance that this happens (what if your inventory numbers are correct, but someone at the warehouse drops a box and breaks the last item?). Businesses have been dealing with these sort of problems far longer than transactions existed. Therefore, talk to business and ask what you should do when an order comes in and you are out of stock. If proper stock management is in place, you'll probably know already when new items will be available, so you can email the customer with the new delivery date and offer a refund if she doesn't accept (as an example).

As a side note, your sentence "an order microservice which then emits events to deduct inventory from the inventory microservice" sounds wrong. It might be just the sentence wording, but just in case, I want to point out that the wording suggests that what you do is emitting a Command, not an Event. A command is imperative, you tell someone to do something (DeductInventoryCommand). An Event is something that has already happened (OrderPlacedEvent).

Ideally, you don't want one microservice telling another one what to do, as this effectively breaks the autonomy of the service that receives the command. Instead, develop your services to be able to perform their business capability based on the information they "hear" from other microservices, but autonomously. A Logistics service can listen to the placed orders (and cancelled orders) and take care of all the logistics process without being told what to do. Billing should be able to do its job without being told what to do either, and so on.

Related Topic