1

I'm learning cqrs right now.

Problem

My concern is event ordering.

Assumption 1: user service is creating user and three events in order. Each event was sent after each step was finished in write model.

  • Event A: user ABC created
  • Event B: changed email for user ABC to abc@mail
  • Event C: change email for user ABC to abc2@mail

Assumption 2: I have 3 instances of same microservice read app that will handle this events.

Without ordering email can be changed before account was created or even worse eventB can override eventC email value.

I found two things: vector clock and consuming history not event.

vector clock --> forces me to create complicated logic to handle that and will decrease performance

consuming history not event --> not solve the problem. Consumer 1 can take 100 events ...-B but consumer 2 will take events C-... and still will try to override email in non existing account.

Question:

What are best practices to handle this case?

Krzysztof
  • 123
  • 1
    How can an unexisting account (A) be changed (B)? The timeline makes it impossible. On the other hand, events are "things that already happened"- The problem is not with handling these events in order but with triggering the events at the right time. Are you perhaps describing commands and not events? – Laiv Jan 02 '23 at 10:02
  • Clarified in question content. – Krzysztof Jan 02 '23 at 10:13
  • 2
    @Laiv, he's perhaps not suggesting that the impossible would occur, but that the attempt to maintain the record of an account that didn't yet exist, would either cause a crash, cause a lost update, or require some sort of suspended processing with complicated logic to resolve. Obviously there are fundamental faults with his approach, but I imagine he thinks the events will be triggered by a clientside user, but the alterations will be presented to a central store in an out-of-order fashion, and with no coordinated checkpoints between the user and the central store to ensure ordering of events. – Steve Jan 02 '23 at 12:24
  • The core technology in play here for the specific problem you're asking about is event sourcing, not CQRS. They are often used together but they are two different things and the order of stackable events is specifically ES-related. I'm making this comment since you are currently learning CQRS and you may benefit from reading up on event sourcing as well since that's exactly what you're trying to do here. – Flater Jan 02 '23 at 22:48

2 Answers2

4

This shouldn't come as a surprise to you, but complicated architectural patterns like CQRS and distributed systems aren't free. It is more effort to build a first version when using more complicated patterns than it is just to throw everything into a monolith and process everything sequentially - but it gives advantages in the longer term (in theory anyway. Quite possibly you ain't gonna need it).

If it is an issue for you that the solutions need you "to create complicated logic" then don't use this kind of solution.

  • As I wrote Im learning this pattern. If use of vector clock and wait for another instances until they will consume previous event ( with whole logic for timeout) is one of the best way to solve problem, it means that it's what I want to hear. I want to avoid situation where I will spend a lot of time implementing this and then some one will tell me that you can done this simpler and quicker with this and that. – Krzysztof Jan 02 '23 at 10:04
  • 1
    This is why I dislike "learning a pattern" without an actual use case. Everything has tradeoffs, and you can only evaluate those tradeoffs if you have an actual use case where you can say "okay, for my use case it's more important to do X than Y". Vector clocks are the right answer for some use cases but not others; there isn't a general "do this in every case" answer to this problem (or any other one, for that matter). – Philip Kendall Jan 02 '23 at 10:20
  • I will never introduce to real project something that I don't understand. And to understand it I need to learn it somewhere to see what challenges it will introduce to me. there isn't a general "do this in every case - I agree. This is why I asked for common practices how you do this in real life so I can read about and analyze them to have a reference in future. Before your previous comment I was even uncertain if vector clocs are something that I should look into. – Krzysztof Jan 02 '23 at 10:36
  • 3
    there isn't a general "do this in every case then, there can't be "common practices". How we handle these cases in real life depends on the real-life needs and resources at hand. For example, you could validate that C is not processed before B, if you include the former and the new email in the same event and your consumer checks that, in fact, the "transition" correspond to the current data it has. if former email <> current email in my db then ignore the transition. What to do with these "ignored" events is a whole different subject. Eventual consistency is a beast to feed apart. – Laiv Jan 02 '23 at 10:42
  • 1
    @Krzysztof, the problem is that you're approaching learning patterns as if the real challenge is knowing all the abstract details of it. Like if you had a hammer, you're learning how it's forged, what shaped heads exist, what materials they can be made of, etc. Expert tradesmen and craftsmen who use hammers often couldn't tell you offhand how exactly the handle is attached to the head - they are experts in knowing when and how to wield hammers. You are learning hammers without any attention at all to learning how to swing one. Even hammer-makers, first learn how hammers are used. – Steve Jan 02 '23 at 12:52
  • 1
    there are absolutely "common practices" and you can study them even in cases where they don't make sense, but often the study is useless in these cases because it just seems like a way to make the project more complicated for no reason. I encountered this with ECS in gamedev, until I figured out a kind of ECS that really clicked for a particular game. – user253751 Jan 02 '23 at 19:36
2

I think you are confusing CQRS and Event Sourcing.

in CQRS, you achieve distribution for your reads, but the writes all goto the same DB. Thus you can use transactions and other standard practices to keep the ordering of changes as they are all commands.

Event Sourcing usually requires as you say a vector clock, or simplified version of such, as the events are not processed in strictly chronological order or by the same machines.

However, some events are just going to clash and you will be forced to have a reject mechanism. ie. in your example even if you say C should be the last event due to chronological ordering, if both users have been presented with state A prior to making their change then both events have the same ancestral ordering and really one should be rejected. "Sorry your version of the record is out of date, please redo with the latest version"

Ewan
  • 75,506