design-patternsunit-of-work

How should the UnitOfWork pattern be actually handled?


What should a UoW be actually handled?

The way I see it, the UoW should not be handling commits and rollbacks. It should just provide means to do so, and should only be responsible with tracking changes on objects that are about to be committed such that if they are in some inconsistent state or something has changed, the transaction should fail?

So the way I see a UoW is something like this.

public interface IUnitOfWork : IDisposable
{
    IAtomicTransaction BeginTransaction();

    Task<object> SaveAsync<TEntity>(TEntity entity);

    Task UpdateAsync<TEntity>(TEntity entity);

    Task RemoveAsync<TEntity>(TEntity entity);
}

Committing should be something like this:

interface IAtomicTransaction : IDisposable
{
    void Commit();

    void Rolleback();
}

Take for example this post (and not only this but there are many like it),

Unit of Work + Repository Pattern in MVC5 and Web API 2 with Fluent NHibernate and Ninject

If you look you will see that it uses ISession within the repository, which I find it to be a mistake since it will directly link the Repository (your business) to NHibernate's ISession. Shouldn't the UoW take this responsibility? Should you start changing your business implementation because you change the framework? Shouldn't the UoW act as an Adapter, something like an anti-corruption layer?


Solution

  • As now you have edited your question, I have to change the way I answer; hence the second answer. My first answer is still valuable (hopefully ;)).

    UoW should automatically figure out what (if any) changes needs to be flushed.

    IAtomicTransaction BeginTransaction();
    void Commit();
    void Rolleback();
    

    The above methods may or may not be the part of UoW. Many implementations of UoW expose those. The drawback of exposing those is that, transaction handling becomes responsibility of the caller; not your class. The plus point is that, the caller gets better control over the process.

    If you want to bypass the drawback, see the alternative in my first answer.

    Task<object> SaveAsync<TEntity>(TEntity entity);
    Task UpdateAsync<TEntity>(TEntity entity);
    Task RemoveAsync<TEntity>(TEntity entity);
    

    The above methods are part of Repositories. Those cannot be the part of UoW. UoW should automatically figure out what to do based on change tracking. If we limit our discussion with just database transaction, then DbTransaction handles this correctly. For more detailed ways to handle change tracking, please refer to my first answer.

    Following is the NHibernate-based implementation. Please note that this is not recommended. If you are using a full ORM, you should avoid these type of implementations as they add little value to your design. If you simply replace ISession with IDbConnection, this could be implemented with ADO.NET as well

    public interface IUnitOfWork : IDisposable
    {
        void Flush();
    }
    
    public sealed class UnitOfWork : IUnitOfWork
    {
        public UnitOfWork()
        {
            session = SessionFactroyHelper.CreateSession();
            transaction = session.BeginTransaction();
        }
    
        ISession session = null;
        ITransaction transaction = null;
    
        void IUoWSession.Flush()
        {
            transaction.Commit();
        }
    
        void IDisposable.Dispose()
        {
            transaction.Dispose();
            transaction = null;
            session.Dispose();
            session.Dispose();
        }
    }
    

    By the way, this topic itself is opinion-based. How to implement UoW and Repositories is an individual design decision. If you are really keen about implementing correct(?) UoW, consider using some advanced ORM directly in the code bypassing the UoW wrapper.