A new feature in the big Marten 7.0 release this weekend is an alternative to add numeric revisions to a document as a way of enforcing optimistic concurrency checks.
First off, from Martin Fowler’s seminal Patterns of Enterprise Application Architecture (which is just visible to me on my book case across the room as I write this even though it’s 20 years old now), an Optimistic Offline Lock is:
Prevents conflicts between concurrent business transactions by detecting a conflict and rolling back the transaction.
David Rice
In a simple usage, let’s say we’re building some kind of system to make reservations for restaurants. Logically, we’d have a document named Reservation
, and we’ve decided that we want to use the numeric revisioning on this document. That document type could look something like this:
// By implementing the IRevisioned
// interface, we're telling Marten to
// use numeric revisioning with this
// document type and keep the version number
// on the Version property
public class Reservation: IRevisioned
{
public Guid Id { get; set; }
// other properties
public int Version { get; set; }
}
Now, let’s see this in action just a little bit:
public static async Task try_revisioning(IDocumentSession session, Reservation reservation)
{
// This will create a new document with Version = 1
session.Insert(reservation);
// "Store" is an upsert, but if the revisioned document
// is all new, the Version = 1 after changes are committed
session.Store(reservation);
// If Store() is called on an existing document
// this will just assign the next revision
session.Store(reservation);
// *This* operation will enforce the optimistic concurrency
// The supplied revision number should be the *new* revision number,
// but will be rejected with a ConcurrencyException when SaveChanges() is
// called if the version
// in the database is equal or greater than the supplied revision
session.UpdateRevision(reservation, 3);
// This operation will update the document if the supplied revision
// number is greater than the known database version when
// SaveChanges() is called, but will do nothing if the known database
// version is equal to or greater than the supplied revision
session.TryUpdateRevision(reservation, 3);
// Any checks happen only here
await session.SaveChangesAsync();
}
Summary
In the end, this is another alternative to the older Guid
based version tracking that Marten has supported since 1.0. I don’t know about you, but I can certainly read and understand an integer much more easily than a random string of letters, numbers, and dashes.
In reality though, this feature was specifically built as a prerequisite to some serious improvements to the asynchronous projection support in Marten. Time and ambition permitting, the next Marten 7.0 blog post will show how Marten can support the strongly consistent “write model” projections you need for command processing while also being performant and allowing for zero downtime projection rebuilds.
I’ve built my own versioning system on top of Marten before, using optimistic concurrency – really nice to see official document versioning support, Marten somehow keeps getting better 🎉