How do I encapsulate the saving of more than one entity in a transactional manner using the repository pattern? For example, what if I wanted to add an order and update the customer status based on that order creation, but only do so if the order completed successfully? Keep in mind that for this example, orders are not a collection inside the customer. They are their own entity.
This is just a contrived example, so I don’t really care whether orders should or should not be inside the customer object or even in the same bounded context. I don’t really care what underlying technology will be used (nHibernate, EF, ADO.Net, Linq, etc.) I just want to see what some calling code might look like in this admittedly contrived example of an all or nothing operation.
Booting my computer this morning I faced the exact problem for a project I am working on. I had some ideas which lead to the following design - and comments would be more than awesome. Unfortunately the design suggested by Josh isn't possible, as I have to work with a remote SQL server and can't enable the Distribute Transaction Coordinator service it relies on.
My solution is based on a few yet simple changes to my existing code.
First, I have all my repositories implement a simple marker interface:
Secondly, I let all my transaction enabled repositories implement the following interface:
The idea is that in all my repositories I implement this interface and add code which introduces transaction directly depending on the actual provider (for fake repositories I have made a list of delegates which gets executed on commit). For LINQ to SQL it would be easy to make implementations such as:
This of course requires that a new repository class is created for each thread, but this is reasonable for my project.
Each method using the repository needs to invoke the
BeginTransaction()
and theEndTransaction()
, if the repository implementsIHasTransactions
. To make this call even easier, I came up with the following extensions:Comments are appreciated!
Using Spring.NET AOP + NHibernate you can write your repository class as normal and configure your transactions in custom XML file:
In the XML file you select which methods you would like to be executed inside a transaction:
And in your code you obtain an instance of the CustomerService class like this:
Spring.NET will return you a proxy of the CustomerService class that will apply a transaction when you call CreateOrder method. This way there's no transaction specific code inside your service classes. AOP takes care of it. For more details you can take a look at the documentation of Spring.NET.
You want to look at implementing the unit of work pattern. There are implementations out there for NHibernate. One is in the Rhino Commons project, there's also the Machine.UoW.
Its not a responsibility of the repository, its usually something done at a higher level. Although you said your not interested in specific technologies I think its worth tying down the solutions, for example when using NHibernate with a Web app you'd probably consider using session-per request.
So if you can manage transactions at a higher level then my two options would be:
If you go for the second option then the question is what happens to the in-memory objects, your Customer might be left in an inconsistent state. If that matters, and I work in scenarios where it doesn't as the object was only loaded in for that request, then I'd be considering the upfront check if its possible because its a lot easier than the alternatives (rolling back the in-memory changes or reloading the objects).
I would look at using some type of Transaction Scope / Context system. So you might have the following code which is roughly based on .Net & C#.
TransactionScope can nest so let's say you had an action which crossed multiple services your application would create a TransactionScope as well. Now in the current .net if you use the TransactionScope they have you risk escallating to a DTC but this will be resolved in the future.
We had created our own TransactionScope class which basically managed our DB connections and used local SQL transactions.