How to manage transactions in the service layer?
We’re developing a .Net application with the following architecture: presentation layer (using MVC pattern with ASP.Net MVC 2), service layer, data access layer (using repository pattern over Entity Framework).
We’ve decided to put the transaction management in the service layer but we’re not sure about how to implement it. We want to control the transaction entirely at the service layer level. That is, every time a controller calls a method in the service layer, it has to be an atomic operation regarding database updates.
If there were no relation between different services provided in the service layer, then it would be simple: each method should commit the changes at the end of its execution (that is, call the save method on the context it uses). But sometimes services at the service layer work together.
e.g.: we provide a shipment service that has a confirm method which receives the following parameters: the shipment id, a flag indicating if it corresponds to a new customer or an existing one, the customer id (in case the shipment confirmation is for an existing customer) and a customer name (in case it is for a new customer). If the flag is set to "new customer", then the service layer has to (a) create the customer and (b) confirm the shipment. For (a) the shipment service calls the customer service (which already implements the validations and logic needed to create a new customer and store it in the database).
Who should commit the changes in this scenario?
- Should the customer service do it? it cann开发者_如何学Goot commit the changes after creating the new customer, because something can go wrong later in the shipment confirmation method, but it has to commit its changes in the case it is call directly (in other use case, provided to create a client).
- Should the controller calling the service method do it? but the controller shouldn’t know anything about transactions, we've decided to put all transaction knowladge in the service layer.
- A Transaction Manager in the services layer? How to design it?, who calls it and when?
Is there a design pattern for this that we should follow?
I have a Commit() on my service, this only commits if the UnitOfWork is created by the service, if it is passed in the constructor the commit does nothing.
I used a second (internal) constructor for the service:
public class MyService
{
private IUnitOfWork _uow;
private bool _uowInternal;
public MyService()
{
_uow = new UnitOfWork();
_uowInternal = false;
}
internal MyService(IUnitOfWork uow)
{
_uow = uow;
_uowInternal = true;
}
public MyServiceCall()
{
// Create second service, passing in my UnitOfWork:
var svc2 = new MySecondService(_uow);
// Do your stuff with both services.
....
// Commit my UnitOfWork, which will include all changes from svc2:
Commit();
}
public void Commit()
{
if(!_uowInternal)
_uow.Commit();
}
}
In a similar architecture with WCF and L2S instead of EF, we chose to use transactions in the main service interface implementation class. We used TransactionScope to achieve this:
public void AServiceMethod() {
using(TransactionScope ts = new TransactionScope()) {
service1.DoSomething();
service2.DoSomething();
ts.Complete();
}
}
The main disadvantage is that the transaction may get big. In that case, if for example one of the service calls in the transaction block requires only readonly access, we wrap it in a nested TransactionScope(TransactionScopeOption.Suppress)
block to prevent locking rows/tables further in the transaction lifetime.
精彩评论