patterncsharpModerate
Reusable Unit Of Work Interface / Factory
Viewed 0 times
factoryinterfacereusableworkunit
Problem
Given my
I then create an abstract factory interface called
I then create a default implementation of my
I then create the factory to return that implementation called
The reason for creating the factory is to allow DI (Dependency Injection) frameworks to use different unit of work implementations depending on configuration.
If
IUnitOfWork interface using System;
public interface IUnitOfWork : IDisposable
{
void Commit();
}I then create an abstract factory interface called
IUnitOfWorkFactoryusing System.Transactions;
public interface IUnitOfWorkFactory
{
IUnitOfWork GetUnitOfWork(IsolationLevel isolationLevel);
}I then create a default implementation of my
IUnitOfWork called TransactionScopeUnitOfWorkusing System;
using System.Transactions;
public class TransactionScopeUnitOfWork : IUnitOfWork
{
private bool disposed = false;
private readonly TransactionScope transactionScope;
public TransactionScopeUnitOfWork(IsolationLevel isolationLevel)
{
this.transactionScope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions
{
IsolationLevel = isolationLevel,
Timeout = TransactionManager.MaximumTimeout
});
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
this.transactionScope.Dispose();
}
disposed = true;
}
}
public void Commit()
{
this.transactionScope.Complete();
}
}I then create the factory to return that implementation called
TransactionScopeUnitOfWorkFactoryusing System.Transactions;
public class TransactionScopeUnitOfWorkFactory : IUnitOfWorkFactory
{
public IUnitOfWork GetUnitOfWork(IsolationLevel isolationLevel)
{
return new TransactionScopeUnitOfWork(isolationLevel);
}
}The reason for creating the factory is to allow DI (Dependency Injection) frameworks to use different unit of work implementations depending on configuration.
If
TransactionScopeUnitOfWorkFactory was mappedSolution
Leaky Abstractions
I like that there's only a
I would move
Then I'd change the abstract factory interface to look something like this:
And the specific implementation that creates a
With IoC Containers such as Ninject, you can easily specify
That said I've noticed I seldom need to actually implement an abstract factory with Ninject: you can easily configure DI to inject the result of an anonymous method whenever a class has an
The client code shouldn't be bothered with configuring a transaction isolation level if all it needs to care for is that it's depending on an
public interface IUnitOfWork : IDisposable
{
void Commit();
}I like that there's only a
Commit method here - it makes the IUnitOfWork interface very well segregated/focused. However the interface specifying that all implementations must also implement IDisposable, is a leaky abstraction - you have a specific implementation in mind, and that implementation is leaking into the abstraction: what if I wanted a mock-up implementation that doesn't need to be disposed?public interface IUnitOfWorkFactory
{
IUnitOfWork GetUnitOfWork(IsolationLevel isolationLevel);
}GetUnitOfWork makes it sound like the "factory" is hiding a Singleton or something (the name is reminiscent of "GetInstance") - I like my abstract factories when they expose a single parameterless Create method, and intake their dependencies from the constructor; IsolationLevel is also an implementation detail that's leaking into an abstraction - one specific implementation requires an "isolation level", and that implementation detail is leaked into the abstraction.I would move
: IDisposable to TransactionScopeUnitOfWork, because that specific implementation must implement IDisposable.Then I'd change the abstract factory interface to look something like this:
public interface IUnitOfWorkFactory
{
IUnitOfWork Create();
}And the specific implementation that creates a
TransactionScopeUnitOfWork would be responsible for specifying an IsolationLevel:public class TransactionScopeUnitOfWorkFactory : IUnitOfWorkFactory
{
private readonly IsolationLevel _isolationLevel;
public TransactionScopeUnitOfWorkFactory(IsolationLevel isolationLevel)
{
_isolationLevel = isolationLevel;
}
public IUnitOfWork Create()
{
return new TransactionScopeUnitOfWork(_isolationLevel);
}
}With IoC Containers such as Ninject, you can easily specify
IsolationLevel.Serializable or IsolationLevel.ReadCommitted as needed, depending on whether the factory dependency is being injected into a Smurf or into a FooBar's constructor.That said I've noticed I seldom need to actually implement an abstract factory with Ninject: you can easily configure DI to inject the result of an anonymous method whenever a class has an
IUnitOfWork dependency to be constructor-injected: you just do the binding with .ToFactory and let Ninject do the hard work.The client code shouldn't be bothered with configuring a transaction isolation level if all it needs to care for is that it's depending on an
IUnitOfWork that provides a Commit method; if the client code needs to know about an isolation level, then it knows more than it should about the implementation behind the interface it's presented.Code Snippets
public interface IUnitOfWork : IDisposable
{
void Commit();
}public interface IUnitOfWorkFactory
{
IUnitOfWork GetUnitOfWork(IsolationLevel isolationLevel);
}public interface IUnitOfWorkFactory
{
IUnitOfWork Create();
}public class TransactionScopeUnitOfWorkFactory : IUnitOfWorkFactory
{
private readonly IsolationLevel _isolationLevel;
public TransactionScopeUnitOfWorkFactory(IsolationLevel isolationLevel)
{
_isolationLevel = isolationLevel;
}
public IUnitOfWork Create()
{
return new TransactionScopeUnitOfWork(_isolationLevel);
}
}Context
StackExchange Code Review Q#77610, answer score: 16
Revisions (0)
No revisions yet.