13 Aralık 2014 Cumartesi

Unit Of Work

The Unit of Work pattern is designed to maintain a list of business objects that have been changed by a business transaction, whether by adding, removing, or updating. The Unit of Work then coordinates the persistence of the changes and any concurrency problems flagged. The benefit of utilizing the Unit of Work in your DAL is to ensure data integrity; if an issue arises partway through persisting a series of business objects as part of a transaction, all changes should be rolled back to ensure that the data remains in a valid state.
To demonstrate the Unit of Work pattern, you will be using a simple banking domain to model the transfer of funds between two accounts. Image shows the interaction between the service layer and the repository layer using the Unit of Work pattern to ensure that the transfer commits as one atomic Unit of Work.
The Unit of Work structure in this example is based on the framework that Tim McCarthy uses in his book .NET Domain-Driven Design with C#: Problem-Design-Solution.

Image shows the classes that you will create in this exercise and exactly how they relate to each other to make unit of work pattern.

Create a new solution named ASPPatterns.Chap7.UnitOfWork and add the following class library projects:

  • ASPPatterns.Chap7.UnitOfWork.Infrastructure
  • ASPPatterns.Chap7.UnitOfWork.Model
  • ASPPatterns.Chap7.UnitOfWork.Repository

Right-click on the ASPPatterns.Chap7.UnitOfWork.Model and add a project reference to the ASPPatterns.Chap7.UnitOfWork.Infrastructure project. Right-click on the ASPPatterns .Chap7.UnitOfWork.Repository project and add a project reference to the ASPPatterns.Chap7 .UnitOfWork.Infrastructure and the ASPPatterns.Chap7.UnitOfWork.Model projects.

You will start the solution by creating all the infrastructure code to support the Unit of Work pattern. Add a new interface to the infrastructure named IAggregateRoot with the following contract:
    public interface IAggregateRoot
    {

    }
The IAggregateRoot interface is actually a pattern in itself called the marker interface pattern. The interface acts as meta data for a class and methods that interact with instances of that class test for the existence of the interface before carrying out their work. You will see this pattern used later in this chapter when you build a repository layer that will only persist business objects that implement the IAggregateRoot interface. 

The Unit of Work implementation will use the IAggregateRoot interface to reference any business entity that is partaking in an atomic transaction. Add another interface to the Infrastructure project named IUnitOfWorkRepository, with the contract listing that follows:
    public interface IUnitOfWorkRepository
    {
        void PersistCreationOf(IAggregateRoot entity);
        void PersistUpdateOf(IAggregateRoot entity);
        void PersistDeletionOf(IAggregateRoot entity);
    }
The IUnitOfWorkRepository is a second interface that all Repositories are required to implement if they intend to be used in a Unit of Work. You could have added this contract definition to the model Repository interface that you will add later, but the interfaces are addressing two different types of concerns. This is the definition of the Interface Segregation principle. 

Finally, add a third interface to the Infrastructure project named IUnitOfWork, the definition of which you can find here:
    public interface IUnitOfWork
    {
        void RegisterAmended(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository);
        void RegisterNew(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository);
        void RegisterRemoved(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository);
        void Commit();
    }
 The IUnitOfWork interface requires the IUnitOfWorkRepository when registering an amend/ addition/ deletion so that, on commitment, the Unit of Work can delegate the work of the actual persistence method to the appropriate concrete implementation. The logic behind the IUnitOfWork methods will become a lot clearer when you look at a default implementation of the IUnitOfWork interface, which is what you are going to do next. 

Add a new class to the Infrastructure project named UnitOfWork, and update the newly created class with the following code:

    public class UnitOfWork : IUnitOfWork
    {
        private Dictionary addedEntities;
        private Dictionary changedEntities;
        private Dictionary deletedEntities;

        public UnitOfWork()
        {
            addedEntities = new Dictionary();
            changedEntities = new Dictionary();
            deletedEntities = new Dictionary();
        }

        public void RegisterAmended(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository)
        {
            if (!changedEntities.ContainsKey(entity))
            {
                changedEntities.Add(entity, unitOfWorkRepository);
            }
        }

        public void RegisterNew(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository)
        {
            if (!addedEntities.ContainsKey(entity))
            {
                addedEntities.Add(entity, unitOfWorkRepository);
            }
        }

        public void RegisterRemoved(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository)
        {
            if (!deletedEntities.ContainsKey(entity))
            {
                deletedEntities.Add(entity, unitOfWorkRepository);
            }
        }

        public void Commit()
        {
            using (TransactionScope scope = new TransactionScope())
            {
                foreach (IAggregateRoot entity in this.addedEntities.Keys)
                {
                    this.addedEntities[entity].PersistCreationOf(entity);
                }

                foreach (IAggregateRoot entity in this.changedEntities.Keys)
                {
                    this.changedEntities[entity].PersistUpdateOf(entity);
                }

                foreach (IAggregateRoot entity in this.deletedEntities.Keys)
                {
                    this.deletedEntities[entity].PersistDeletionOf(entity);
                }

                scope.Complete();
            }
        }
    }
You are required to add a reference to System.Transactions so you can use the TransactionScope class, which will ensure the persistence will commit in an atomic transaction. The UnitOfWork class uses three dictionaries to track pending changes to business entities. The first dictionary corresponds to entities to be added to the data store. The second dictionary tracks entities to be updated, and the third deals with entity removal. A matching IUnitOfWorkRepository is stored against the entity key in the dictionary and is used in the Commit method to call the Repository, which will contain the code to actually persist an entity. The Commit method loops through each dictionary and calls the appropriate IUnitOfWorkRepository method passing a reference to the entity. The work in the Commit method is wrapped in a TransactionScope using block; this ensures that no work is done until the TransactionScope Complete method is called. If an exception occurs while you are performing work within the IUnitOfWorkRepository, all work is rolled back, and the data store is left in its original state.

To demonstrate the Unit of Work pattern in action, you will build a simple bank account domain to handle transfers between two accounts. Add a new class to the Model project named Account. The Account class represents a bank account and contains a single property to hold the account balance. The code listing for this class is shown here:
    public class Account : IAggregateRoot
    {
        public decimal Balance { get; set; }
    }
To enable persistence of the Account, you will add a cut down version of a Repository interface containing the methods relevant to this example. Create a new interface within the Model project named IAccountRepository with the following contract:
    public interface IAccountRepository
    {
        void Save(Account account);
        void Add(Account accont);
        void Remove(Account account);
    }
There is no need to add contract definitions for Account retrieval, because this demonstration will not use them. 

To complete the model, you will create a service class to coordinate the transferring of monies between two accounts. Add a new class named AccountService with the following code:
    public class AccountService
    {
        private IAccountRepository _accountRepository;
        private IUnitOfWork _unitOfWork;

        public AccountService(IAccountRepository accountRepository, IUnitOfWork unitOfWork)
        {
            _accountRepository = accountRepository;
            _unitOfWork = unitOfWork;
        }

        public void Transfer(Account from, Account to, decimal amount)
        {
            if (from.Balance >= amount)
            {
                from.Balance -= amount;
                to.Balance += amount;
                _accountRepository.Save(from);
                _accountRepository.Save(to);
                _unitOfWork.Commit();
            }
        }
    }
The AccountService requires an implementation of the IAccountRepository and IUnitOfWork via its constructor. (Dependency Injection.) The Transfer method checks that the transfer of funds can take place before adjusting the balances of each account. It then calls the account Repository to save both accounts. Finally, it calls the Commit method of the Unit of Work instance to ensure that the transaction is completed as an atomic Unit of Work. So how do the Repository and Unit of Work interact? Well, you’ll stub out an implementation of the Account Repository to find out.

Add a new class named AccountRepository to the Repository project and update it with the following code listing:
public class AccountRepository : IAccountRepository, IUnitOfWorkRepository
    {
        private IUnitOfWork _unitOfWork;

        public AccountRepository(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }

        public void Save(Account account)
        {
            _unitOfWork.RegisterAmended(account, this);
        }

        public void Add(Account accont)
        {
            _unitOfWork.RegisterNew(accont, this);
        }

        public void Remove(Account account)
        {
            _unitOfWork.RegisterRemoved(account, this);
        }

        public void PersistCreationOf(IAggregateRoot entity)
        {
            throw new NotImplementedException();
        }

        public void PersistUpdateOf(IAggregateRoot entity)
        {
            throw new NotImplementedException();
        }

        public void PersistDeletionOf(IAggregateRoot entity)
        {
            throw new NotImplementedException();
        }
    }

The AccountRepository implements both the Model.IAccountRepository and the Infrastructre .IUnitOfWorkRepository interfaces. The implementation of the IAccountRepository methods simply delegates work to the Unit of Work, passing the entity to be persisted along with a reference to the Repository, which of course implements the IUnitOfWorkRepository. As seen previously when the Unit of Work’s Commit method is called, the Unit of Work refers to the Repository’s implementation of the IUnitOfWorkRepository contract to perform the real persistence requirements.

For brevity, and to keep the example simple and easy to follow, the ADO.NET code to persist the Account entity has been omitted. Note that the Unit of Work implementation is injected into the Repository via its constructor. This allows many Repositories to share the Unit of Work, because some transactions will span more than one Repository.



Hiç yorum yok: