SHM
SHM

Reputation: 1952

Should latest event version be queried in event sourcing?

I am developing a simple DDD + Event sourcing based app for educational purposes.

In order to set event version before storing to event store I should query event store but my gut tells that this is wrong because it causes concurrency issues.

Am I missing something?

Upvotes: 1

Views: 685

Answers (2)

A Ralkov
A Ralkov

Reputation: 1044

I guess you shouldn't to think about event version.

If you talk about the place in the event stream, in general, there's no guaranteed way to determine it at the creation moment, only in processing time or in event-storage.

If it is exactly about event version (see http://cqrs.nu/Faq, How do I version/upgrade my events?), you have it hardcoded in your application. So, I mean next use case:

First, you have an app generating some events. Next, you update app and events are changed (you add some fields or change payload structure) but kept logical meaning. So, now you have old events in your ES, and new events, that differ significantly from old. And to distinguish one from another you use event version, eg 0 and 1.

Upvotes: 1

VoiceOfUnreason
VoiceOfUnreason

Reputation: 57267

There are different answers to that, depending on what use case you are considering.

Generally, the event store is a dumb, domain agnostic appliance. It's superficially similar to a List abstraction -- it stores what you put in it, but it doesn't actually do any work to satisfy your domain constraints.

In use cases where your event stream is just a durable record of things that have happened (meaning: your domain model does not get a veto; recording the event doesn't depend on previously recorded events), then append semantics are fine, and depending on the kind of appliance you are using, you may not need to know what position in the stream you are writing to.

For instance: the API for GetEventStore understands ExpectedVersion.ANY to mean append these events to the end of the stream wherever it happens to be.

In cases where you do care about previous events (the domain model is expected to ensure an invariant based on its previous state), then you need to do something to ensure that you are appending the event to the same history that you have checked. The most common implementations of this communicate the expected position of the write cursor in the stream, so that the appliance can reject attempts to write to the wrong place (which protects you from concurrent modification).

This doesn't necessarily mean that you need to be query the event store to get the position. You are allowed to count the number of events in the stream when you load it, and to remember how many more events you've added, and therefore where the stream "should" be if you are still synchronized with it.

What we're doing here is analogous to a compare-and-swap operation: we get a representation of the original state of the stream, create a new representation, and then compare and swap the reference to the original to point instead to our changes

oldState = stream.get()
newState = domainLogic(oldState)
stream.compareAndSwap(oldState, newState)

But because a stream is a persistent data structure with append only semantics, we can use a simplified API that doesn't require duplicating the existing state.

events = stream.get()
changes = domainLogic(events)
stream.appendAt(count(events), changes)

If the API of your appliance doesn't allow you to specify a write position, then yes - there's the danger of a data race when some other writer changes the position of the stream between your query for the position and your attempt to write. Data obtained in a query is always stale; unless you hold a lock you can't be sure that the data hasn't changed at the source while you are reading your local copy.

Upvotes: 6

Related Questions