Setting an explicit expected version when appending to a stream stops the idempotency check?

When appending an event to a stream, if you set the expected version (of the stream) to something other than “Any” it appears to prevent any idempotency checks. The event will be appended to the stream even if it shares the same event ID with an event already in the stream.

In the code below “SerializeToJsonByteEncoded()” is an extension method to deal with JSON-ification and encoding.

This test is successful:

[Test]
public void IdempotencyTest1()
{
    Guid eventIdToBeReused = Guid.NewGuid();
    object msg = new object();
    Dictionary<string, object> metadata = new Dictionary<string, object>();
    IEventStoreConnection eventStoreConnection = EventStoreConnection.Create(new IPEndPoint(IPAddress.Loopback, 1113));
    eventStoreConnection.ConnectAsync().Wait();
    EventData eventData1 = new EventData(eventIdToBeReused, "object", true, msg.SerializeToJsonByteEncoded(), metadata.SerializeToJsonByteEncoded());
    eventStoreConnection.AppendToStreamAsync(eventIdToBeReused.ToString("D"), ExpectedVersion.Any, eventData1).Wait();
    EventData eventData2 = new EventData(eventIdToBeReused, "object", true, msg.SerializeToJsonByteEncoded(), metadata.SerializeToJsonByteEncoded());
    eventStoreConnection.AppendToStreamAsync(eventIdToBeReused.ToString("D"), ExpectedVersion.Any, eventData2).Wait();
    StreamEventsSlice streamEventsSlice = eventStoreConnection.ReadStreamEventsForwardAsync(eventIdToBeReused.ToString("D"), 0, 200, false).Result;
    Assert.AreEqual(1, streamEventsSlice.Events.Length);
}
The number of events in the stream is 1. The second event was not appended because it has the same event ID as the first.
This test fails:

[Test]
public void IdempotencyTest2()
{
Guid eventIdToBeReused = Guid.NewGuid();
object msg = new object();
Dictionary<string, object> metadata = new Dictionary<string, object>();
IEventStoreConnection eventStoreConnection = EventStoreConnection.Create(new IPEndPoint(IPAddress.Loopback, 1113));
eventStoreConnection.ConnectAsync().Wait();
EventData eventData1 = new EventData(eventIdToBeReused, “object”, true, msg.SerializeToJsonByteEncoded(), metadata.SerializeToJsonByteEncoded());
eventStoreConnection.AppendToStreamAsync(eventIdToBeReused.ToString(“D”), ExpectedVersion.Any, eventData1).Wait();
EventData eventData2 = new EventData(eventIdToBeReused, “object”, true, msg.SerializeToJsonByteEncoded(), metadata.SerializeToJsonByteEncoded());
eventStoreConnection.AppendToStreamAsync(eventIdToBeReused.ToString(“D”), 0, eventData2).Wait();
StreamEventsSlice streamEventsSlice = eventStoreConnection.ReadStreamEventsForwardAsync(eventIdToBeReused.ToString(“D”), 0, 200, false).Result;
Assert.AreEqual(1, streamEventsSlice.Events.Length);
}


The number of events in the stream is 2. The second event is appended even though it has the same event ID as the first. The only difference between the tests is that on the second append the expected version is changed from "ExpectedVersion.Any" to "0".

Having "0" as the expected version is valid because there is one event in the stream (and Event Store uses zero-based indexes).

I would like to set the expected version to guard against concurrency.

Any help?

Thanks,

Callum

Its checks the event at expected version.

So how do I guard against concurrency and be idempotent? I need an expected version for a concurrency check.

Thanks,

Callum

Try retrying with the *same* expected version it will be idempotent.

I am using deterministic GUIDs for my event IDs. So a repeat of the same operation will result in an event with the same event ID. I was hoping to use this to have inherently idempotent operations.

This leads to the following scenario:

  • Operation 1 - Read stream, do some logic
  • Expected stream version = x
  • Write Event A
  • Operation 2 - Read stream, do some logic
  • Expected stream version = x + 1
  • Write Event B (some other process)
  • Operation 3 (retry/replay of 1) - Read stream, do some logic
  • Expected stream version = x + 2
  • Write Event A (again, same event ID)
    For operation 3, I can’t set the expected stream version to what it was for operation 1 because other stuff has happened. The expected version has changed because there was other activity on the stream. Operation 3 (the retry/replay of operation 1) might still happen because of a at-least-once delivery mechanics somewhere. The operation gives the same result, so same event ID due to deterministic GUIDs.

Thoughts?

Callum

write {A expected version 12}
write {A expected version 14}

This is not a retry.

Although if you are doing the read on each step why wouldn't you see
the event from operation 1 when reading for operation 3?