Reputation: 8928
I have an abstract base class for command/query handlers:
public abstract class AbstractBusiness<TRequest, TResult> : IBusiness<TRequest, TResult>
where TResult : BaseResponse
{
public TResult Send(TRequest request)
{
TResult response = Activator.CreateInstance<TResult>();
response.Success = true;
try
{
IConnectionProvider connectionProvider =
ServiceLocator.Current.GetInstance<IConnectionProvider>();
using (DatabaseScope scope = DatabaseScopeManager.Instance.GetScope(
connectionProvider,
DatabaseScopeType.UseExistingOrNewTX,
IsolationLevel.ReadCommitted))
{
response = this.Handle(request);
scope.Complete();
}
}
catch (Exception exc)
{
response.Success = false;
response.Message = exc.Message;
}
return response;
}
protected abstract TResult Handle(TRequest request);
}
This is how it is used:
public sealed class AccountCreateBusiness
: AbstractBusiness<AccountCreateRequest, AccountCreateResponse>
{
private readonly IMessageProvider messageProvider;
public AccountCreateBusiness(IMessageProvider messageProvider)
{
this.messageProvider = messageProvider;
}
protected override AccountCreateResponse Handle(AccountCreateRequest request)
{
//handle request
}
}
I'm trying to get rid of the ServiceLocator
in the base class. Is there a way to inject IConnectionProvider
without having to change all derivative classes to include IConnectionProvider
in their constructors and calling : base(connectionProvider)
? I can't change DatabaseScopeManager
.
Upvotes: 1
Views: 328
Reputation: 172646
The core of the problem here is the existence of your base class. You should get rid of the base class. Removing the base class will remove your problem completely.
The base class is problematic because:
Although you can use Property Injection to get rid of the Service Locator, this doesn't remove the stated problems and even introduces a new problem, which is: Temporal Coupling.
Instead you should simply get rid of the base class and move these cross-cutting concerns into decorators. In your case I suggest creating two decorators.
A TransactionBusinessDecorator
:
public class TransactionBusinessDecorator<TRequest, TResult>
: IBusiness<TRequest, TResult>
where TResult : BaseResponse
{
private readonly IConnectionProvider connectionProvider;
private readonly IBusiness<TRequest, TResult> decoratee;
public TransactionBusinessDecorator(
IConnectionProvider connectionProvider,
IBusiness<TRequest, TResult> decoratee)
{
this.connectionProvider = connectionProvider;
this.decoratee = decoratee;
}
public TResult Send(TRequest request)
{
TResult response;
using (DatabaseScope scope = DatabaseScopeManager.Instance.GetScope(
this.connectionProvider,
DatabaseScopeType.UseExistingOrNewTX,
IsolationLevel.ReadCommitted))
{
response = this.decoratee.Handle(request);
scope.Complete();
}
return response;
}
}
And a ExceptionWrapperBusinessDecorator
:
public class ExceptionWrapperBusinessDecorator<TRequest, TResult>
: IBusiness<TRequest, TResult>
where TResult : BaseResponse
{
private readonly IBusiness<TRequest, TResult> decoratee;
public ExceptionWrapperBusinessDecorator(
IBusiness<TRequest, TResult> decoratee)
{
this.decoratee = decoratee;
}
public TResult Send(TRequest request)
{
TResult response = Activator.CreateInstance<TResult>();
try
{
var response = this.decoratee.Handle(request);
response.Success = true;
}
catch (Exception ex)
{
response.Success = false;
response.Message = exc.Message;
}
return response
}
}
This allows the AccountCreateBusiness
to be written as follows:
public sealed class AccountCreateBusiness
: IBusiness<AccountCreateRequest, AccountCreateResponse>
{
private readonly IMessageProvider messageProvider;
public AccountCreateBusiness(IMessageProvider messageProvider)
{
this.messageProvider = messageProvider;
}
public AccountCreateResponse Handle(AccountCreateRequest request)
{
//handle request
}
}
Wrapping every handler with the decorators is trivial with most DI containers and when doing this by hand (a.k.a. Pure DI).
Was last design advice. Catching every exception and returning this information as part of the message is typically a bad idea, because:
Upvotes: 2
Reputation: 2708
As Matías Fidemraizer mentioned, you may use Property Injection which is a quite safe way to avoid breaking changes. The drawback is that the property dependency in optional, but in your code the IConnectionProvider
is a must.
In the long running perspective, I'd go with two constructors:
[Obsolete]
to indicate that it'll be removed in the next major version.IConnectionProvider
as a must-have dependency. This one should be recommended for use and should became the only one as soon as all the usages of the default constructor are eliminated.Upvotes: 1
Reputation: 64923
There's an alternative: property injection.
BTW, property injection doesn't define mandatory dependencies, while constructor dependencies do it.
Upvotes: 1