Imagine if it was possible, when faced with a potential concurrency issue to programmatically check if any previous ‘events’ actually conflict with the current action being requested. With a CQRS Event Sourced application, it is possible to significantly reduce the problems of concurrency and in the remainder of this article I will show you how.
What is a Concurrency Conflict?
A concurrency issue occurs when at least 2 users of a system are making changes at around the same time. For example, Joe and Harry have just got back to the office after a client meeting. They both bring up the client record from the CRM. As the meeting went well and is likely to lead to new business Joe increases the likely income for the client. At the same time Harry writes up his notes for the meeting. In a traditional CRUD based system, if no concurrency strategy is in place (last in wins) then whoever presses save last will overwrite the update of the other person.
An Event Sourced Concurrency Strategy allows fine Grained Control Allowing us to Avoid Most Concurrency Conflicts
Event sourcing gives you the ability to check for concurrency conflicts in a more granular way. Given the scenario above, Joe would have issued an UpgradeClientValue command which would have raised a ClientValueUpgraded event. Harry would have issued a AddMeetingNote command which in turn would have raised a MeetingNoteAdded event. When the events are saved your code would have detected via a version field that one of the events was raised with the wrong version number (i.e. one back from where it should have been). At this point you can build a simple lookup registry to see what events actually conflict with the each other. For example, ClientValueUpgraded would conflict with itself and with ClientValueDownGraded but not with MeetingNoteAdded and despite being one version behind, can be allowed through without overwriting any data.
The following code is an example of an approach to building up a conflict registry.
The basic idea is to create a registry at application startup and register any special cases. By default if a type that has not been registered will throw a concurrency error if it is fired with an old version number. This way you don’t need to register all the events within your application. Certain events will never conflict, in which case you can register it with an empty conflicts list. Then, no matter how far behind it is it will be allowed through. Here is an example of how the code is called:
This fine grained control allows your system to avoid a higher percentage of potential concurrency issues and thus serves your users much better.