Transactions in the Repository Pattern
Asked Answered
I

7

60

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.

Inflated answered 22/2, 2009 at 16:10 Comment(0)
R
21

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:

/// <summary>
/// A base interface for all repositories to implement.
/// </summary>
public interface IRepository
{ }

Secondly, I let all my transaction enabled repositories implement the following interface:

/// <summary>
/// Provides methods to enable transaction support.
/// </summary>
public interface IHasTransactions : IRepository
{
    /// <summary>
    /// Initiates a transaction scope.
    /// </summary>
    void BeginTransaction();

    /// <summary>
    /// Executes the transaction.
    /// </summary>
    void CommitTransaction();
}

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:

#region IHasTransactions Members

public void BeginTransaction()
{
    _db.Transaction = _db.Connection.BeginTransaction();
}

public void CommitTransaction()
{
    _db.Transaction.Commit();
}

#endregion

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 the EndTransaction(), if the repository implements IHasTransactions. To make this call even easier, I came up with the following extensions:

/// <summary>
/// Extensions for spawning and subsequently executing a transaction.
/// </summary>
public static class TransactionExtensions
{
    /// <summary>
    /// Begins a transaction if the repository implements <see cref="IHasTransactions"/>.
    /// </summary>
    /// <param name="repository"></param>
    public static void BeginTransaction(this IRepository repository)
    {
        var transactionSupport = repository as IHasTransactions;
        if (transactionSupport != null)
        {
            transactionSupport.BeginTransaction();
        }
    }

    public static void CommitTransaction(this IRepository repository)
    {
        var transactionSupport = repository as IHasTransactions;
        if (transactionSupport != null)
        {
            transactionSupport.CommitTransaction();
        }
    }
}

Comments are appreciated!

Rangel answered 23/2, 2009 at 11:51 Comment(4)
You could also go with a variant and create a repository instance for each transaction, put it inside a using statement and let the Dispose() commit the transaction. This would abstract away need to know about the transaction in the caller method.Rangel
Just for the record I believe you can use a transactionscope and it will not escalate to a DTC so long as your only using a single SQL Server. And I believe this has changed at somepoint in the last 4 yearsDie
What about having a transaction to span multiple repository methods, then what?Alcazar
Then, create separate repository which will begin, commit, rollback transactions and call it from upper layer.Irremovable
D
13

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#.

public class OrderService
{    
    public void CreateNewOrder(Order order, Customer customer)
    {
      //Set up our transactional boundary.
      using (TransactionScope ts=new TransactionScope())
      {
        IOrderRepository orderRepos=GetOrderRespository();
        orderRepos.SaveNew(order);
        customer.Status=CustomerStatus.OrderPlaced;
    
        ICustomerRepository customerRepository=GetCustomerRepository();
        customerRepository.Save(customer)
        ts.Commit();   
       }
    }
}

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.

Die answered 22/2, 2009 at 16:20 Comment(2)
I don't think this is a solution in spirit of DDD. Basically you have created a transaction script which does the job of Domain Model. Service shouldn't change customer status, for example.Jacksmelt
Something in the code has to handle this business rule, be it at this level or a higher level the point was doing the changes within a single TransactionScope allowing either local transactions or distributed transactions to handle the transaction. If the business rule says update the customer whenever an order is placed then this is a good place to handle that as all orders go through here.Die
O
6

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.

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:

  1. Upfront check - For example in a service co-ordinating the behavior decide if you want to proceed by asking the Order/Customer, if either say they don't then don't even try to update either of them.
  2. Rollback - Just proceed updating the Customer/Order and if things fail part way through rollback the database transaction.

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).

Odaodab answered 22/2, 2009 at 18:14 Comment(2)
Why is it not the responsibility of the Repository? Isn't the whole idea to abstract database operations away from the domain model? To me, the repository is the best place to put that transactional support.Inflated
It's not the responsibility of the Repository, because you can have a transaction spanning multiple repositories, and in such case, it makes more sense to have a separate interface for transactions.Pantelegraph
S
5

Using Spring.NET AOP + NHibernate you can write your repository class as normal and configure your transactions in custom XML file:

public class CustomerService : ICustomerService
{
    private readonly ICustomerRepository _customerRepository;
    private readonly IOrderRepository _orderRepository;

    public CustomerService(
        ICustomerRepository customerRepository, 
        IOrderRepository orderRepository) 
    {
        _customerRepository = customerRepository;
        _orderRepository = orderRepository;
    }

    public int CreateOrder(Order o, Customer c) 
    {
        // Do something with _customerRepository and _orderRepository
    }
}

In the XML file you select which methods you would like to be executed inside a transaction:

  <object id="TxProxyConfigurationTemplate" 
          abstract="true"
          type="Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data">

    <property name="PlatformTransactionManager" ref="HibernateTransactionManager"/>

    <property name="TransactionAttributes">
      <name-values>
        <add key="Create*" value="PROPAGATION_REQUIRED"/>
      </name-values>
    </property>
  </object>

  <object id="customerService" parent="TxProxyConfigurationTemplate">
    <property name="Target">
      <object type="MyNamespace.CustomerService, HibernateTest">
          <constructor-arg name="customerRepository" ref="customerRepository" />
          <constructor-arg name="orderRepository" ref="orderRepository" />
      </object>
    </property>

  </object>

And in your code you obtain an instance of the CustomerService class like this:

ICustomerService customerService = (ICustomerService)ContextRegistry
    .GetContent()
    .GetObject("customerService");

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.

Sheehy answered 22/2, 2009 at 16:36 Comment(0)
P
3

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.

Pearlene answered 22/2, 2009 at 16:22 Comment(0)
D
2

You can add a transaction parameter to the end of methods that you want to run in a transaction and give it a default value of null. Thus, if you don't want to run the method in an existing transaction then leave off the end parameter or explicitly pass null.

Inside these methods you can check the parameter for null to determine whether to create a new transaction or else use one passed in. This logic can be pushed to a base class.

This keeps your methods purer than when using a context based solution.

void Update(int itemId, string text, IDbTransaction trans = null) =>
   RunInTransaction(ref trans, () =>
   {
      trans.Connection.Update("...");
   });

void RunInTransaction(ref IDbTransaction transaction, Action f)
{
    if (transaction == null)
    {
        using (var conn = DatabaseConnectionFactory.Create())
        {
            conn.Open();

            using (transaction = conn.BeginTransaction())
            {
                f();

                transaction.Commit();
            }
        }
    }
    else
    {
        f();
    }
}

Update(1, "Hello World!");
Update(1, "Hello World!", transaction);

Then you can have a transaction runner for your service layer...

public class TransactionRunner : ITransactionRunner
{
    readonly IDatabaseConnectionFactory databaseConnectionFactory;

    public TransactionRunner(IDatabaseConnectionFactory databaseConnectionFactory) =>
        this.databaseConnectionFactory = databaseConnectionFactory;

    public void RunInTransaction(Action<IDbTransaction> f)
    {
        using (var conn = databaseConnectionFactory.Create())
        {
            conn.Open();

            using (var transaction = conn.BeginTransaction())
            {
                f(transaction);

                transaction.Commit();
            }
        }
    }

    public async Task RunInTransactionAsync(Func<IDbTransaction, Task> f)
    {
        using (var conn = databaseConnectionFactory.Create())
        {
            conn.Open();

            using (var transaction = conn.BeginTransaction())
            {
                await f(transaction);

                transaction.Commit();
            }
        }
    }
}

And a service method might look like this...

void MyServiceMethod(int itemId, string text1, string text2) =>
   transactionRunner.RunInTransaction(trans =>
   {
      repos.UpdateSomething(itemId, text1, trans);
      repos.UpdateSomethingElse(itemId, text2, trans);
   });

Which is easy to mock for unit testing...

public class MockTransactionRunner : ITransactionRunner
{
    public void RunInTransaction(Action<IDbTransaction> f) => f(null);
    public Task RunInTransactionAsync(Func<IDbTransaction, Task> f) => f(null);
}
Doorplate answered 1/6, 2019 at 17:8 Comment(0)
G
0

From Eric Evans, DDD Book, CH 6, Repositories :

Leave transaction control to the client. Although the REPOSITORY will insert into and delete from the database, it will ordinarily not commit anything. It is tempting to commit after saving, for example, but the client presumably has the context to correctly initiate and commit units of work. Transaction management will be simpler if the REPOSITORY keeps its hands off.

Is nice to let a higher layer do the transactional management:

  • When dealing with two or more aggregate roots and you must ensure that both are in a consistent state.
  • The client code usually have more context to act when there is an error on the repository operation.
  • The Repository keeps focused on the task of retrieving/updating a specific entity, usually an aggregate root.
Genitor answered 7/10, 2021 at 3:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.