Design patterns got a bad rap when they were horrendously overused in the decade plus following the publication of the gang of four book. I’m still a believer in learning design patterns. I even think it’s valuable to know and understand the “official” names for patterns. One, because it’s really nice for other developers to understand the jargon that you’re using to describe possible solutions, but mostly so that you can easily go Google for a lot more information about how, when, and when not to use it later as you need to know more.
That being said, I think it’s useful for new users to Marten to understand the old “Unit of Work” design pattern, both conceptually and how Marten implements the pattern.
Jeremy’s Law of Nerd-Rage: a group of developers being enthusiastic about anything related to development will eventually make a second group of developers angry and cause a backlash.
Unit of Work
From Martin Fowler’s PEAA book:
Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.
The unit of work pattern is a simple way to collect all the changes to a backing data store you need to be committed in a single transaction. It’s especially useful if you need to have several unrelated pieces of code collaborating on the same logical transaction. If you just pass around a “unit of work” container for the changes to each “worker” object or function, you can keep the various things completely decoupled from each other and still collaborate on a single business transaction.
In Marten, the unit of work is buried behind our IDocumentSession interface (that might change later). When you register changes to an IDocumentSession (inserts, updates, deletes, appending events, etc.), these changes are only staged until the SaveChanges() method is called to persist all the pending changes in one database transaction.
You can see Marten’s unit of work mechanics in action in the code sample below:
// theStore is a DocumentStore using (var session = theStore.OpenSession()) { // All I'm doing here is recording references // to all the ADO.Net commands executed by // this session var logger = new RecordingLogger(); session.Logger = logger; // Insert some new documents session.Store(new User {UserName = "luke", FirstName = "Luke", LastName = "Skywalker"}); session.Store(new User {UserName = "leia", FirstName = "Leia", LastName = "Organa"}); session.Store(new User {UserName = "wedge", FirstName = "Wedge", LastName = "Antilles"}); // Delete all users matching a certain criteria session.DeleteWhere<User>(x => x.UserName == "hansolo"); // deleting a single document by Id, if you had one session.Delete<User>(Guid.NewGuid()); // Persist in a single transaction session.SaveChanges(); // All of this was done in one batched command // in the same transaction logger.Commands.Count.ShouldBe(1); // I'm just writing out the Sql executed here var sql = logger.Commands.Single().CommandText; new FileSystem() .WriteStringToFile("unitofwork.sql", sql); }
All of the database changes above are made in a single database call within one transaction (I added extra new lines to make it readable):
select mt_upsert_user(doc := :p0, docId := :p1); select mt_upsert_user(doc := :p2, docId := :p3); select mt_upsert_user(doc := :p4, docId := :p5); delete from mt_doc_user as d where d.data ->> 'UserName' = :arg6; delete from mt_doc_user where id=:p6;
As for how to consume IDocumentSession’s and how to scope them for transactional boundary management, see my blog post on transactions with RavenDb. My intention is for us to use the same conceptual setup with Marten at work.
Typically, I would recommend using an IDocumentSession per HTTP request or within the handling for a single service bus message. We even build our basic infrastructure around this concept. You still need an easy way to use explicit transaction boundaries with a new IDocumentSession on demand. I wrote about our transaction management strategies with RavenDb a couple years ago, and my intention is that we’ll use Marten in a very similar manner.
4 thoughts on “The Unit of Work Pattern in Marten”