Expected Version explained
There are 3 values that you can use for providing expected_version when publishing an event in a stream: :any, an Integer, :auto.
:any
event_store.publish(event, stream_name: "Order-1", expected_version: :any)
Guarantees
- When there are many threads (or processes) writing concurrently (at the same time) to a stream with
expected_versionset to:any, all of those writes should succeed.- When you later try to read those events you from a stream, you don't have a guarantee about particular order you are going to get them in.
- Example: If 3 processes A,B,C publish events to stream
foo-1at the same time, all of them should succeed. When you read events from streamfoo-1you will get events A,B,C or A,C,B or B,A,C or B,C,A or C,A,B or C,B,A.
- Should never fail
- When a single thread (or process) writes events A, B to a stream, it is guaranteed you will retrieve events A,B in that exact order when reading from a stream.
Usage
- This is a default value in RES when
expected_versionis not provided - Good for technical streams
- Good if you use RES as pub-sub and only sometimes you read the events for debugging purposes
- Good if exact order events is not critical
Integer
You start by publishing the first event in a stream with expected_version being -1 (or :none). That means you expect no events in the stream right now.
event_store.publish(
event0,
stream_name: "Order-1",
expected_version: -1, # or :none which is a synonym
# for -1
)
The first published event is at position 0. When you publish a second event you provide expected_version: 0.
event_store.publish([event1, event2], stream_name: "Order-1", expected_version: 0)
We published the second and third events. Their positions are 1 and 2. That's why when you publish the next event you need to provide expected_version: 2.
event_store.publish(event3, stream_name: "Order-1", expected_version: 2)
In other words when you say expected_version: 2 that means: When I publish those events I expect the last event in the stream to be exactly at the position 2. So you expect exactly 3 events to be in that stream at positions 0, 1, 2. Not less, not more.
If the expected_version does not match and another thread (or process) wrote different events in the meantime your write will fail with RubyEventStore::WrongExpectedEventVersion exception. This exception is thrown before any handlers are invoked.
This mode effectively acts as optimistic locking.
Guarantees
- When there are many threads (or processes) writing concurrently (at the same time) to a stream with
expected_versionset to the same correct number, only one of those writes will succeed. - When a single thread (or process) writes events A, B to a stream, it is guaranteed you will retrieve events A,B in that exact order when reading from a stream.
- Succeeds when there were no other successful concurrent writes, raises
RubyEventStore::WrongExpectedEventVersionotherwise (also aliased asRailsEventStore::WrongExpectedEventVersion).
Usage
- good for Event Sourcing
- this is what
aggregate_rootgem is using
- this is what
- good if you need deterministic, exact order of events in a stream even when there are multiple, concurrent events being published.
:auto
event_store.publish(event, stream_name: "Order-1", expected_version: :auto)
:auto is like explicitly providing the number for expected_version but RES will automatically find the position of last event in a stream.
There is a potential for a race condition between reading the expected_version and providing it for a write. That's why this mode only makes sense if you always use your custom, application specific locking mechanism around (using mutexes or custom DB locks).
application_lock("Order-1") do
# do something with Order 1...
event_store.publish(event, stream_name: "Order-1", expected_version: :auto)
end
The guarantees mentioned below assume there is no application specific lock.
Guarantees
- When there are many threads (or processes) writing concurrently (at the same time) to a stream with
expected_versionset to the same correct number, at least one of those writes will succeed. - When a single thread (or process) writes events A, B to a stream, it is guaranteed you will retrieve events A,B in that exact order when reading from a stream.
- Succeeds when there were no other successful concurrent writes, raises
RubyEventStore::WrongExpectedEventVersionotherwise (also aliased asRailsEventStore::WrongExpectedEventVersion).
Usage
- good if you need deterministic, exact order of events in a stream even when there are multiple, concurrent events being published. But you already have a custom layer of locks which prevents potential concurrency issues.
Beware
- You should never mix
expected_version: :anywith:autoor explicit number (Integer) for the same stream name. - These described semantics are valid since
v0.19and forrails_event_store_active_recordadapter (which has always been the default).