Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transactions in the Repository Pattern

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.

like image 556
Charles Graham Avatar asked Feb 22 '09 16:02

Charles Graham


People also ask

What is repository transaction?

A repository is a logical collection of entities that define the business process workflow. The Transaction Repository includes the following entities: Transactions. Conditions. Actions.

What are repository patterns?

The Repository pattern. Repositories are classes or components that encapsulate the logic required to access data sources. They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer.

How does repository pattern work?

The idea behind the Repository pattern is to decouple the data access layer from the business access layer of the application so that the operations (such as adding, updating, deleting, and selecting items from the collection) is done through straightforward methods without dealing with database concerns such as ...

What is repository pattern in PHP?

According to Edward Hieatt and Rob Mee, a repository mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects. A more straightforward way includes creating one layer rather than writing the function at the controller right away.


2 Answers

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!

like image 115
Troels Thomsen Avatar answered Sep 28 '22 05:09

Troels Thomsen


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.

like image 44
JoshBerke Avatar answered Sep 28 '22 05:09

JoshBerke