Patterns of Enterprise Application Architecture (The Addison-Wesley Signature Series)
by Martin Fowler

Read more about this book...

Defines Unit of Work as:

"Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems." - PoEAA

I've been playing with some different ideas on how you can implement a unit of work in a win forms application.

Here was the idea of the usage:

 1 public void SomeMethod() {
 2       using (var unitOfWork = UnitOfWork.StartFor<IPerson>())
 3       {
 4           var stacey = new Person(&quot;stacey&quot;);
 5           var veronica = new Person(&quot;veronica&quot;);
 6           var betty = new Person(&quot;betty&quot;);
 7   
 8           stacey.NewNumberIs(&quot;312-7467&quot;);
 9   
10           unitOfWork.Commit();
11       }
12   }

When the unit of work is asked to commit the new and modified instance would be committed to the person repository, in this case my imaginary black book.

 1 public class BlackBook : IRepository<IPerson> {
 2       private IList<IPerson> associates;
 3   
 4       public BlackBook() : this(new List<IPerson>()) {
 5       }
 6   
 7       public BlackBook(IList<IPerson> associates) {
 8           this.associates = associates;
 9       }
10   
11       public void Add(IPerson newAssociate) {
12           associates.Add(newAssociate);
13       }
14   
15       public void Update(IPerson updatedAssociate) {
16       }
17   }

Here's how it works... Person inherits from "DomainSuperType". In the layer super type the no argument constructor registers itself with the current unit of work. I really don't like this because it makes all the domain objects aware of the surrounding infrastructure, and makes it much more difficult to test.

Next all components have to be decorated with the "Serializable" attribute, so that I could manage dirty object tracking. This also sucks...

 1 [Serializable]
 2   public class DomainSuperType<T> where T : class {
 3       public DomainSuperType() {
 4           UnitOfWork.StartFor<T>().Register(this as T);
 5       }
 6   }
 7   public interface IPerson
 8   {
 9       void NewNumberIs(string newNumber);
10   }
11   
12   [Serializable]
13   public class Person : DomainSuperType<IPerson>, IPerson {
14       private string name;
15       private string knownPhoneNumber;
16   
17       public Person(string name) {
18           this.name = name;
19       }
20   
21       public void NewNumberIs(string newNumber) {
22           knownPhoneNumber = newNumber;
23       }
24   }

The unit of work delegates to a registry of units of work to retrieve the unit of work applicable to type ....

1 public static class UnitOfWork {
2       public static IUnitOfWork<T> StartFor<T>() {
3           return Resolve.DependencyFor<IUnitOfWorkRegistry>().StartUnitOfWorkFor<T>();
4       }
5   }

The unit of work registry creates a unit of work for type if one hasn't been started yet. Otherwise returns the already started unit of work. This registry is similar to an identity map using type T as the identifier.

 1 public class UnitOfWorkRegistry : IUnitOfWorkRegistry {
 2       private IDictionary<Type, object> unitsOfWork;
 3       private IUnitOfWorkFactory factory;
 4   
 5       public UnitOfWorkRegistry(IUnitOfWorkFactory factory) {
 6           this.factory = factory;
 7           unitsOfWork = new Dictionary<Type, object>();
 8       }
 9   
10       public IUnitOfWork<T> StartUnitOfWorkFor<T>() {
11           if (unitsOfWork.ContainsKey(typeof (T)))
12           {
13               return (IUnitOfWork<T>) unitsOfWork[typeof (T)];
14           }
15           var unitOfWork = factory.CreateFor<T>();
16           unitsOfWork.Add(typeof (T), unitOfWork);
17           return unitOfWork;
18       }
19   }

The unit of work factory leverages the dependency resolver to retrieve an implementation of the repository applicable to type T.

 1 public class UnitOfWorkFactory : IUnitOfWorkFactory {
 2       private IDependencyResolver resolver;
 3   
 4       public UnitOfWorkFactory(IDependencyResolver resolver) {
 5           this.resolver = resolver;
 6       }
 7   
 8       public IUnitOfWork<T> CreateFor<T>() {
 9           return new WorkSession<T>(resolver.GetMeAnImplementationOf<IRepository<T>>());
10       }
11   }

Each time the unit of work factory is asked to create a new unit of work it creates a fresh instance of a work session.

 1 public class WorkSession<T> : IUnitOfWork<T> {
 2       public WorkSession(IRepository<T> repository) : this(repository, new ObjectToRegisteredObjectMapper()) {
 3       }
 4   
 5       public WorkSession(IRepository<T> repository, IObjectToRegisteredObjectMapper mapper) {
 6           this.mapper = mapper;
 7           this.repository = repository;
 8           registeredInstances = new HashSet<IRegisteredInstanceOf<T>>();
 9       }
10   
11       public void Register(T newInstanceToRegister) {
12           registeredInstances.Add(mapper.MapFrom(newInstanceToRegister));
13       }
14   
15       public void Commit() {
16           foreach (var registeredInstance in registeredInstances)
17           {
18               registeredInstance.CommitTo(repository);
19           }
20       }
21   
22       public void Dispose() {
23           registeredInstances = new HashSet<IRegisteredInstanceOf<T>>();
24       }
25   
26       private readonly IRepository<T> repository;
27       private ICollection<IRegisteredInstanceOf<T>> registeredInstances;
28       private IObjectToRegisteredObjectMapper mapper;
29   }
 1 public class RegisteredInstance<T> : IRegisteredInstanceOf<T> {
 2     private readonly T originalInstance;
 3     private readonly T workingInstance;
 4 
 5     public RegisteredInstance(T newInstanceToRegister, ICloner cloner) {
 6         workingInstance = newInstanceToRegister;
 7         originalInstance = cloner.Clone(newInstanceToRegister);
 8     }
 9 
10     public T Original() {
11         return originalInstance;
12     }
13 
14     public T WorkingCopy() {
15         return workingInstance;
16     }
17 
18     public bool HasBeenModified() {
19         return !Original().Equals(WorkingCopy());
20     }
21 
22     public void CommitTo(IRepository<T> repository) {
23         if (HasBeenModified()) {
24             repository.Update(WorkingCopy());
25         }
26         else {
27             repository.Add(WorkingCopy());
28         }
29     }
30 
31     protected bool Equals(RegisteredInstance<T> registered) {
32         return registered != null &amp;&amp; Equals(originalInstance, registered.originalInstance);
33     }
34 
35     public override bool Equals(object obj) {
36         return ReferenceEquals(this, obj) || Equals(obj as RegisteredInstance<T>);
37     }
38 
39     public override int GetHashCode() {
40         return originalInstance != null ? originalInstance.GetHashCode() : 0;
41     }
42 }

Each registered instance immediately clones the original instance to keep track of changes between the original and the current working copy. For this to work properly the cloner has to perform a deep copy otherwise the dirty tracking wont work properly. To do the deep copy, i'm using serialization, hence the "serializable" attribute decorating each entity.

1 public class Cloner : ICloner
2   {
3       public T Clone< T >( T instanceToClone )
4       {
5           var serializer = new Serializer< T >( );
6           return serializer.DeserializeFrom( serializer.Serialize( instanceToClone ) );
7       }
8   }

So far this implementation is just a spike on how to implement a unit of work, it's really not a great implementation but I'm hoping to solicit some feedback on ways that have worked for others.

comments powered by Disqus