1

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?

mat-mcloughlin
  • 6,492
  • 12
  • 45
  • 62
SHM
  • 1,896
  • 19
  • 48
  • It will not cause concurrency issues *if* you have a constraint in the Event store (i.e. an unique constraint on the `aggregateType-aggregateId-version`) – Constantin Galbenu Apr 11 '18 at 18:05
  • not sure if storing aggregate type in event store is a good idea, the columns are : 'aggregateId, version, eventData' – SHM Apr 12 '18 at 09:04
  • It depends on your design. In mine design, the same ID value can be shared by different aggregate types. – Constantin Galbenu Apr 12 '18 at 09:54
  • What kind of event store are you using? – guillaume31 Apr 12 '18 at 13:49
  • @ConstantinGalbenu yeah exactly, because id is shared by multiple aggregates that means we will retrieve the event stream by aggregate id and not event type. (event type will result into explosion of types because that way we have to keep a humongous enum to map type number to the a string value). – SHM Apr 12 '18 at 14:54
  • @guillaume31 this one: https://eventstore.org/ – SHM Apr 12 '18 at 14:55
  • 1
    @SHM I wrote "aggregate type" not "event type" – Constantin Galbenu Apr 12 '18 at 14:57
  • @ConstantinGalbenu oh my bad, how dose storing aggregate type will prevent concurrency? consider a scenario where i query the event version and while trying to increment, another writer will query and increment the version. placing a constraint will just prevent an inconsistent write and in best case just raise an exception from ES. – SHM Apr 12 '18 at 16:04
  • @ConstantinGalbenu about what you said, and wondering if ES can increment the version when appending new event, based on the some thing like a constraint (aggregateType-aggregateId-version) – SHM Apr 12 '18 at 16:08
  • 1
    @SHM If you are using eventstore.org then you are safe; those guys protect your event streams from concurrency issues; you can read more here: https://eventstore.org/docs/dotnet-api/optimistic-concurrency-and-idempotence/index.html – Constantin Galbenu Apr 12 '18 at 16:16
  • @SHM if "set event version" means what I think it means, you don't have to do it manually with geteventstore. The system does it for you. – guillaume31 Apr 12 '18 at 19:40
  • When you use the net api it has expected version on save. This is the version of my aggreagate at the time.. and while saving.. the client sees a newer version it will error. I do also have a method to get latest event using the backwardstream api. I utilise this in the aggregate cache to make sure nobody else updated that stream. If yes I can just get the delta and update the current aggregate, or dump and replay it.. whatever. So yes.. you should be checking versions. Worth reading about idempotency on the event store documentation too. – Piotr Kula Jul 11 '18 at 15:11

2 Answers2

6

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.

VoiceOfUnreason
  • 52,766
  • 5
  • 49
  • 91
1

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.

A Ralkov
  • 1,046
  • 10
  • 19
  • 1
    I think you are talking about versioning models.. and not versions of sequence per se. I think the op is asking about sequencing. I ran into a similar problem. My cache checks the latest version in ES.. if its not the same I dump the cache and then reload the aggregate and store that in cache. – Piotr Kula Sep 19 '18 at 15:19