I'm having an issue with (re)applying events from multiple topics in the correct order on the read / query side.
Example:
On the write / command side, we have 2 Aggregates with an n:m relationship:
- Contact
- Group
Those Aggregates produce the following events on 2 separate event stream topics (because best practices says: One topic per aggregate. And I totally agree):
Contact Topic:
ContactCreated (contactId: "123", name: "Peter")
ContactAddedToGroup (contactId: "123", groupId: "456")
Group Topic:
GroupCreated (groupId: "456", name: "Customers")
On the read / query side (e.g. Elasticsearch) I'd like to execute this query:
- Find all Contacts that belong to any Group which name begins with
Custo...
- Find all Groups which name begins with
Custo...
(this shouldn't be a problem at all)
To achieve this, there are 2 read models. Example Data:
{contactId: "123", name: "Peter", groups: [{id: "456", name: "Customers"}]}
{groupId: "456", name: "Customers"}
The Problem:
The order of events can only be guaranteed for a single Event Topic (like in Apache Kafka). Though the 3 Events can be consumed by the read / query side in multiple ways: 1,2,3
or 1,3,2
or 3,1,2
How to handle 1,2,3
? Database pseudo statements example:
INSERT Contact (contactId: "123", name: "Peter")
FIND Group WHERE (groupId: "456")
(doesn't work, because Group wasn't inserted yet)UPDATE Contact WHERE (contactId: "123") ADD Group (groupId: "456", name: "???")
(here's the problem)
INSERT Group (groupId: "456", name: "Customers")
Idea(s):
I could extend the algorithm and append one more statement. This will lookup all Contacts that have been added to the Group, and add the Group name to those (to make the search query work):
UPDATE Contact WHERE (groupId: "456") REPLACE Group (groupId: "456", name: "Customers")
Another idea (I don't like) could be to only use a single event stream topic. Then the order of events will always be correct. But there will be cases where this won't be easily possible. (Also best practices tell, that one should use one topic per aggregate)
Ignore the problem, because it's pretty unlikely to happen, because the User will provide the necessary delay between Create Group and Add Contact To Group. But when it comes to Event Replay there's no delay, and Event Topics can be consumed in parallel / 'random' order.
Question(s):
This scenario should be fairly common. But unfortunately there are very few real world CQRS examples on the web. And most of them won't explain the small / hidden pitfalls.
How do you solve those problems?