I'm working with the unit of work pattern to glue together my application logic. To create such a unit of work object I use the builder pattern.
But my unit of work classes need access to the service collection of the dependency injection container but as the builder pattern requires you to know how to new the object this won't work. That's why I wrote the Build<T>
method on MyUnitOfWork
which uses reflection and the service container to get the parameters to construct the unit of work.
I know that I could remove MyUnitOfWorkConfiguration
from the unit of work constructors but in the code I'm using this MyUnitOfWorkConfiguration
class is quite big and the change would mean I need to duplicate the properties into the builder and the unit of work class.
public class MyUnitOfWorkConfiguration
{
public bool RunSynchronously { get; set; }
}
public class SpecialUnitOfWorkConfiguration : MyUnitOfWorkConfiguration
{
public bool IgnoreSpecialError { get; set; }
}
public abstract class MyUnitOfWork<TConfiguration> where TConfiguration : MyUnitOfWorkConfiguration
{
protected readonly TConfiguration Configuration;
protected MyUnitOfWork(IMyDependencyIncectedService myDependencyIncectedService, TConfiguration configuration)
{
// do something with the myDependencyIncectedService
Configuration = configuration;
}
protected void ExecuteInner(Action action)
{
if (Configuration.RunSynchronously)
action();
else
Task.Run(action);
}
}
public class LoginUnitOfWork : MyUnitOfWork<MyUnitOfWorkConfiguration>
{
public LoginUnitOfWork(IMyOtherDependencyIncectedService myOtherDependencyIncectedService, IMyDependencyIncectedService myDependencyIncectedService, MyUnitOfWorkConfiguration configuration)
: base(myDependencyIncectedService, configuration)
{
// do something with the myOtherDependencyIncectedService
}
public void Login(string username, string password)
{
ExecuteInner(() =>
{
// do the login
});
}
}
public class SpecialUnitOfWork : MyUnitOfWork<SpecialUnitOfWorkConfiguration>
{
public SpecialUnitOfWork(IMyDependencyIncectedService myDependencyIncectedService, SpecialUnitOfWorkConfiguration configuration)
: base(myDependencyIncectedService, configuration) { }
public void Execute()
{
ExecuteInner(() =>
{
try { /* do special action */ }
catch (SpecialException)
{
if (!Configuration.IgnoreSpecialError) throw;
}
});
}
}
public abstract class MyUnitOfWorkBuilder<TConfiguration> where TConfiguration : MyUnitOfWorkConfiguration, new()
{
protected readonly TConfiguration Configuration = new TConfiguration();
public MyUnitOfWorkBuilder<TConfiguration> RunSynchronously()
{
Configuration.RunSynchronously = true;
return this;
}
public T Build<T>() where T : MyUnitOfWork<TConfiguration>
{
var constructor = typeof(T).GetConstructors().First();
var parameters = constructor.GetParameters();
var parameterValues =
parameters.Select(
parameter =>
parameter.ParameterType.IsAssignableFrom(typeof(MyUnitOfWorkConfiguration))
? Configuration
: ServiceLocator.Current.GetInstance(parameter.ParameterType));
return (T)constructor.Invoke(parameterValues.ToArray());
}
}
public class MyUnitOfWorkBuilder : MyUnitOfWorkBuilder<MyUnitOfWorkConfiguration> { }
public class SpecialUnitOfWorkBuilder : MyUnitOfWorkBuilder<SpecialUnitOfWorkConfiguration>
{
public SpecialUnitOfWorkBuilder IgnoreSpecialError()
{
Configuration.IgnoreSpecialError = true;
return this;
}
}
var loginUnitOfWork = new MyUnitOfWorkBuilder()
.RunSynchronously()
.Build<LoginUnitOfWork>();
loginUnitOfWork.Login("myUserName", "myPassword");
var specialUnitOfWork = new SpecialUnitOfWorkBuilder()
.IgnoreSpecialError()
.RunSynchronously()
.Build<SpecialUnitOfWork>();
specialUnitOfWork.Execute();
Examples for such a unit of work
are:
- ExitApplicationUnitOfWork (logs out users, cleans up, asks if the user is sure etc.)
- LoginUserUnitOfWork (logs the user in)
- LogoutUserUnitOfWork (logs the user out)
- BackupUnitOfWork (backs up stuff)
At the moment there are about 8 of those unit of work types and after the implementation of the new feature set there will be around 15.
My questions are
- Is what I do bad practice?
- Do I follow the rules of the
builder
,unit of work
anddependency injection
pattern? Or is what I did here something strange I came up with?^^ - Could I solve this better?
P.S.: If you have any other inputs to my code please just let me know.
my
s. This does not look like real code. \$\endgroup\$SpecialUnitOfWork
is an example \$\endgroup\$