2

I am trying to program a simple shopping scenario using event sourcing:

---------------     ---------------       ----------------
| OrderService |   | ProductService |    | PaymentService |
----------------   ------------------    -----------------
        |________________|_____________________|
                         |
                --------------------
                |       KAFKA      |
                --------------------
                         |
                --------------------
                |      PROCESSOR   |
                --------------------
                         |
                --------------------
                |     DATABASE     |
                --------------------

There are 3 services:

  • Order Service: API that receives orders for a product made by a customer
  • Product Service: Contains the stock of products
  • Payment Service: Makes the payment of the order

Kafka: event store for the different events.

Processor: CQRS processor that generates the view of the current state.

Database: materialized view of the current state.

My main doubt is how deal with a flow like this:

  1. Order Service is called and creates an event: OrderPlaced
  2. ProductService checks the availability of a product and based on it creates a ProductReserved or OrderCancelledNoStock event.
  3. PaymentService processed the payment in case of ProductReserved event, producing a new event: OrderCompleted.

Since it's a pure event sourcing approach ProductService doesn't have a database.

  • Is it a good practice to consider the materialized view in the DATABASE as the source of thruth of the system regarding the stock? In my view the only source of thruth should be the events, shouldn't it?
  • I see very it likely to find inconsistencies due to the latencies in the DATABASE CQRS process, that's why using the database as the source of thruth is really risky. However having a materialized view seems to be the only way of knowing the current stock of the products and process or not a product order.

Problematic cases that come to my mind:

  • Two concurrent OrderPlaced for the same product arrive at ProductService. This calls DATABASE to check if there's product availability (1 product left). Since that's ok both create a ProductReserved event for the same product and PaymenService will process it.
  • One OrderPlaced order arrives at ProductService. 1 product left so it publishes ProductReserved. Another order arrives for the same product before the DATABASE is updated by the CQRS process. So it publishes another ProductReserved despite not having enough stock.

What's the recommended way of handling a scenario like this?

0

What you have here is a process that spawns multiple services. In DDD, these services are Aggregates, the biggest possible transaction boundary. I will give an answer in the context of the DDD.

You can model this with a Saga/Process manager (OrderProcess) that orchestrate the ordering process from order placed to order completed events.

ProductService checks the availability of a product and based on it creates a ProductReserved or OrderCancelledNoStock event.

Indeed, the ProductService should raise ProductReserved event but not the OrderCancelledNoStock event. This service should not know about orders, only about product reserving - separation of concerns. Instead it should throw a ProductOutOfStock exception.

If the ProductReserved event is raised then the OrderProcess would receive that and would send a command to the PaymentService to make the payment (this depends on actual payment procedure).

If the ProductOutOfStock exception is thrown then OrderProcess would catch it and send the CancelOrder to the OrderService.

Two concurrent OrderPlaced for the same product arrive at ProductService. This calls DATABASE to check if there's product availability (1 product left). Since that's ok both create a ProductReserved event for the same product and PaymenService will process it.

OrderService, if implemented as an event-sourced aggregate, would not permit that by throwing ProductOutOfStock exception after all products would have been reserved. The stock verification is done against the entire stream of previous generated events and not against a projection. In case of concurrent commands execution the last command that tries to persist the events fails to persist them, then the command is retried by re-loading all the events, including the ones generated by the wining command, and then it fails with the ProductOutOfStock exception;

0

As a general rule, command validation should always happen against the principal storage - that is, the event stream. Otherwise, if you validate against an eventually consistent projection, you may become inconsistent, as you have correctly noted.

Sometimes it may make sense to validate in two steps: first against the projection, then against the principal storage. For example, your UI might query the database to show the user an "out of stock" message in case the stock is zero. But once the user has placed the order, the back end would validate it against the event stream.

  • I can't see how to validate the state of the stock against the event store. First, should there be a ProductAdded event per product in the store? Second, how can I process the state of the store to find the real product availability using Kafka? Should this state be kept in memory of the services? Should I reprocess completely every time you want to check it? Wow that's many doubts :-) – codependent Jul 11 '17 at 12:57
  • First, yes, there should be a "product added" event. There should never be any data that your application somehow "knows about", but isn't derivable from the event stream. Event stream must be the principal storage. No other sources of truth. – Fyodor Soikin Jul 11 '17 at 13:03
  • Second, the specific strategy of how to replay the events really depends on your application, I can't give you an advice of that level of specificity. However, keeping the whole event steam in memory is probably not a good idea: it simply won't fit, unless your application is really small and nobody uses it. – Fyodor Soikin Jul 11 '17 at 13:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.