15

When you want code works under race conditions, commonly developers uses Optimistic concurrency control (OCC). From Wikipedia:

...before committing, each transaction verifies that no other transaction has modified the data it has read. If the check reveals conflicting modifications, the committing transaction rolls back...

An approach to implement OCC is checking a version of the data to be modified. If the version differs, then other transactions have modified the data and the it's up to the application to decide how it should resolve the conflict (reattempt, notify user...).

A draft would be as following:

class Repository
{
    public class save($data)
    {
        $currentVersion = $data->version;
        $data->version  = $currentVersion + 1;

        $result = $this->db->update($data, [
            'id'        => $data->id,
            'version'   => $currentVersion
        ]);

        if (1 === $result) {
            // everything ok
        } else {
            // conflict!
        }
    }
}

My question is, as in EventSourcing we only append all events that occurs in the domain, we can't no longer use this approach to implement OCC. Which are other approaches to keep OOC while using EventSourcing?

An option that could works, it's lookup for conflicting events when store them. This approach allow a fine grained control over the events. I don't know if this will over complicated the solution or it's a "standard" that I think is pointed out at http://danielwhittaker.me/2014/09/29/handling-concurrency-issues-cqrs-event-sourced-system/

Any gaps in the problem description is appreciated. Thanks in advance!

martinezdelariva
  • 4,835
  • 2
  • 22
  • 30

3 Answers3

13

Upon trying to append an event to a stream, you can specify an expected current version number and throw if the actual current number doesn't match it.

This kind of optimistic concurrency mechanism is built into some event storage systems.

The article you linked to seems to describe a similar approach but more powerful since you have access to the type of events that occurred since the expected version and can detect conflicts based on more fine-grained criteria.

guillaume31
  • 12,725
  • 28
  • 43
4

By default Optimistic Lock is not applicable for event sourcing, cause Optimistic Lock requires a state to be locked. In event sourcing you don't have anything like state, you just have the list of events(stream of changes). As it was already mentioned a few times before you can use the following approach:

  1. Every event has revision number(it is quite common for events in Event Sourcing approach)
  2. Every time when any event comes and you need to save it you should get the last revision number from events store
  3. Before saving it in event store just increase this revision number
  4. Make sure that you have unique index for revision number in events store
  5. If two parallel transactions increased last revision number(for example 5) and try to save two new events with increased revision number(in our case it is gonna be 6), one of them will fail because of unique index.

Please also keep in mind that your event store should allow you to use unique index, in this case RDBMS tables is the best option.

Anuar Nurmakanov
  • 191
  • 2
  • 14
  • For point 4 & 5. You can only have unique index for revision number in event store when you have only one type of Aggregate? – XuDing Apr 11 '21 at 14:31
3

Applying event-sourcing you also should have a version field in your table otherwise you cannot obtain the order of the events when you build the aggregate root. But the order matters. You can also make use of this version field to support OCC. For instance if you have a race condition like two events with the same ID and the same version will be saved at nearly the same time within the event store the last one looses and a duplicate-key-exception will be raised if you use a composite primary key consisting of the aggregate ID and the version.

ra1f
  • 71
  • 2