20070320

Unit of Work Pattern - Take 2

I attempted the Unit of Work pattern a couple of times (here) and (here)

But I think during the JP bootcamp, we were talking about how the implementation in the Fowler book is very cluttered. Domain objects know about the unit of work, bla, bla bla... I decided to take a stab on implementing a UoW with Domain objects not knowing about the pattern... Before I go, I kind of cheated with making my domain object know how to mark it self.

And if you want the source code, download it (here).

The Test




    1 using System.Collections.Generic;

    2 using MbUnit.Framework;

    3 using Rhino.Mocks;

    4 using unitofwork.spike.Dataaccess;

    5 using unitofwork.spike.Domain;

    6 using unitofwork.spike.utility;

    7 

    8 namespace unitofwork.spike.test.Task

    9 {

   10     [TestFixture]

   11     public class UnitOfWorkTest

   12     {

   13         private MockRepository mockery;

   14         private IDatabaseConnection connection;

   15         private IMapper mapper;

   16 

   17         [SetUp]

   18         public void Setup()

   19         {

   20             mockery = new MockRepository();

   21             mapper = mockery.CreateMock<IMapper>();

   22             connection = mockery.CreateMock<IDatabaseConnection>();

   23             DependencyResolver.Add(typeof (FakeUser), mapper);

   24         }

   25 

   26         [TearDown]

   27         public void TearDown()

   28         {

   29             mockery.VerifyAll();

   30             DependencyResolver.Remove(typeof (FakeUser));

   31         }

   32 

   33         [Test]

   34         public void ShouldInstanciate()

   35         {

   36             mockery.ReplayAll();

   37             IUnitOfWork work = new UnitOfWork(connection);

   38 

   39             Assert.IsNotNull(work);

   40         }

   41 

   42         [Test]

   43         public void Commit_ShouldCommit_OnlyRegisterNew()

   44         {

   45             FakeUser user = new FakeUser();

   46 

   47             mapper.Save(connection, user);

   48             connection.Dispose();

   49 

   50             mockery.ReplayAll();

   51 

   52             IUnitOfWork work = new UnitOfWork(connection);

   53 

   54             work.RegisterNew(user);

   55             work.Commit();

   56         }

   57 

   58         [Test]

   59         public void Commit_ShouldCommitABunchOfStuff()

   60         {

   61             IList<IDomainObject> fakeUsers = new List<IDomainObject>();

   62             FakeUser firstUser = new FakeUser();

   63             FakeUser secondUser = new FakeUser();

   64             FakeUser thirdUser = new FakeUser();

   65             FakeUser fourthUser = new FakeUser();

   66             FakeUser fifthUser = new FakeUser();

   67 

   68             firstUser.Mark = Mark.New;

   69             secondUser.Mark = Mark.Dirty;

   70             thirdUser.Mark = Mark.Delete;

   71 

   72             fakeUsers.Add(firstUser);

   73             fakeUsers.Add(secondUser);

   74             fakeUsers.Add(thirdUser);

   75             fakeUsers.Add(fourthUser);

   76             fakeUsers.Add(fifthUser);

   77 

   78             mapper.Save(connection, firstUser);

   79             mapper.Update(connection, secondUser);

   80             mapper.Delete(connection, thirdUser);

   81 

   82             connection.Dispose();

   83 

   84             mockery.ReplayAll();

   85 

   86             IUnitOfWork work = new UnitOfWork(connection);

   87 

   88             work.Register(fakeUsers);

   89             work.Commit();

   90         }

   91 

   92         private class FakeUser : IDomainObject

   93         {

   94             private Mark mark;

   95 

   96             public Mark Mark

   97             {

   98                 get { return mark; }

   99                 set { mark = value; }

  100             }

  101         }

  102     }

  103 }




The Code




    1 using System.Collections.Generic;

    2 using unitofwork.spike.Dataaccess;

    3 using unitofwork.spike.Domain;

    4 

    5 namespace unitofwork.spike.utility

    6 {

    7     public class UnitOfWork : IUnitOfWork

    8     {

    9         private readonly IDatabaseConnection connection;

   10         private IList<IDomainObject> registerNew;

   11         private IList<IDomainObject> registerDirty;

   12         private IList<IDomainObject> registerDelete;

   13 

   14         public UnitOfWork(IDatabaseConnection connection)

   15         {

   16             this.connection = connection;

   17             registerNew = new List<IDomainObject>();

   18             registerDirty = new List<IDomainObject>();

   19             registerDelete = new List<IDomainObject>();

   20         }

   21 

   22         public UnitOfWork() : this (DependencyResolver.GetImplementationOf<IDatabaseConnection>(typeof(IDatabaseConnection)))

   23         {

   24         }

   25 

   26         public void RegisterNew(IDomainObject domainObject)

   27         {

   28             registerNew.Add(domainObject);

   29         }

   30 

   31         public void RegisterDirty(IDomainObject domainObject)

   32         {

   33             registerDirty.Add(domainObject);

   34         }

   35 

   36         public void RegisterDelete(IDomainObject domainObject)

   37         {

   38             registerDelete.Add(domainObject);

   39         }

   40 

   41         public void Register(IList<IDomainObject> domainObjects)

   42         {

   43             foreach (IDomainObject domainObject in domainObjects)

   44             {

   45                 if (domainObject.Mark != null)

   46                 {

   47                     if (domainObject.Mark == Mark.New)

   48                         RegisterNew(domainObject);

   49 

   50                     if (domainObject.Mark == Mark.Dirty)

   51                         RegisterDirty(domainObject);

   52 

   53                     if (domainObject.Mark == Mark.Delete)

   54                         RegisterDelete(domainObject);

   55                 }

   56             }

   57         }

   58 

   59         public void Commit()

   60         {

   61             using (connection)

   62             {

   63                 try

   64                 {

   65                     Save(connection);

   66                     Update(connection);

   67                     Delete(connection);

   68                 }

   69                 catch

   70                 {

   71                     connection.Rollback();

   72                 }

   73             }

   74         }

   75 

   76         private void Delete(IDatabaseConnection connection)

   77         {

   78             foreach (IDomainObject domainObject in registerDelete)

   79             {

   80                 DependencyResolver.GetImplementationOf<IMapper>(domainObject.GetType()).Delete(connection, domainObject);

   81             }

   82         }

   83 

   84         private void Update(IDatabaseConnection connection)

   85         {

   86             foreach (IDomainObject domainObject in registerDirty)

   87             {

   88                 DependencyResolver.GetImplementationOf<IMapper>(domainObject.GetType()).Update(connection, domainObject);

   89             }

   90         }

   91 

   92         private void Save(IDatabaseConnection connection)

   93         {

   94             foreach (IDomainObject domainObject in registerNew)

   95             {

   96                 DependencyResolver.GetImplementationOf<IMapper>(domainObject.GetType()).Save(connection, domainObject);

   97             }

   98         }

   99     }

  100 }



The other Crap




    1 namespace unitofwork.spike.Domain

    2 {

    3     public interface IDomainObject

    4     {

    5         Mark Mark { get; set; }

    6     }

    7 }




    1 namespace unitofwork.spike.Domain

    2 {

    3     public sealed class Mark

    4     {

    5         public static readonly Mark New = new Mark();

    6         public static readonly Mark Dirty = new Mark();

    7         public static readonly Mark Delete = new Mark();

    8     }

    9 }




    1 using unitofwork.spike.Domain;

    2 

    3 namespace unitofwork.spike.Dataaccess

    4 {

    5     public interface IMapper

    6     {

    7         void Save(IDomainObject domainObject);

    8         void Save(IDatabaseConnection connection, IDomainObject domainObject);

    9         void Update(IDomainObject domainObject);

   10         void Update(IDatabaseConnection connection, IDomainObject domainObject);

   11         void Delete(IDomainObject domainObject);

   12         void Delete(IDatabaseConnection connection, IDomainObject domainObject);

   13     }

   14 }

3 comments:

gutzoft said...

How do you maintain your code formatting in your blog?

Jonas Avellana said...

I think I google'd "ViewSourceAsHtml VS2005". It's an .msi...

Anybody?

SteveG said...

As you said, you cheated :)

So.. that said, how would not cheat ?

What if you had a 'context' object that stored the values on retrieval, then on any update/save/new it would do a compare on the object to see what changed ?

Just a thought