去掉基础中的 ServiceLocator class
Getting rid of the ServiceLocator in the base class
我有 command/query 个处理程序的抽象基础 class:
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);
}
它是这样使用的:
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
}
}
我正试图摆脱基地 class 中的 ServiceLocator
。有没有一种方法可以注入 IConnectionProvider
而不必更改所有派生 classes 以在其构造函数中包含 IConnectionProvider
并调用 : base(connectionProvider)
?我无法更改 DatabaseScopeManager
。
还有一个替代方案:属性 注入。
顺便说一句,属性 注入 没有定义强制依赖项,而构造函数依赖项定义了它。
正如 Matías Fidemraizer 所提到的,您可以使用 属性 注入,这是避免破坏性更改的一种非常安全的方法。缺点是 optional 中的 属性 依赖项,但在您的代码中 IConnectionProvider
是 a must.
从长远的 运行 角度来看,我会选择两个构造函数:
- 默认的 仍然 使用服务定位器(为了向后兼容)但被标记为
[Obsolete]
以指示它将在下一个中删除主要版本。
IConnectionProvider
作为 must-have 依赖项的构造函数。应该推荐使用这个,一旦所有默认构造函数的用法都被淘汰,应该会成为唯一一个。
这里问题的核心是你的基地的存在class。 你应该摆脱底座 class。 移除底座 class 将完全解决你的问题。
基数 class 有问题,因为:
- 它导致每个派生类型都依赖于此 class。
- 它使测试复杂化,因为所有 cross-cutting 问题总是被执行。
- 基地class将成为一个不断增长的大class;单一责任违规。
虽然您可以使用 属性 注入来摆脱服务定位器,但这并没有消除所述问题,甚至引入了一个新问题,即:Temporal Coupling.
相反,您应该简单地摆脱基础 class 并将这些 cross-cutting 问题移到装饰器中。对于您的情况,我建议创建两个装饰器。
一个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;
}
}
还有一个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
}
}
这使得AccountCreateBusiness
可以写成如下:
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
}
}
用装饰器包装每个处理程序对于大多数 DI 容器来说是微不足道的,并且当手动执行此操作时(a.k.a。纯 DI)。
是最后的设计建议。捕获每个异常并将此信息作为消息的一部分 return 通常不是一个好主意,因为:
- 这些错误代码正在泄漏到与其无关的系统基础(您的业务层)。不要在基本响应中提供此信息,而是使用包含此信息的信封。
- 您将向客户提供技术信息。客户通常对这种详细程度不感兴趣。您只想 return 功能消息。
- 安全敏感信息已 returned;黑客喜欢这种信息。
- 如果您不记录此信息,就会丢失很多重要的错误详细信息。
- 基本上 return 错误代码,而不是处理异常。错误代码很容易出错;客户端需要经常检查结果以查看响应是否成功。
我有 command/query 个处理程序的抽象基础 class:
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);
}
它是这样使用的:
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
}
}
我正试图摆脱基地 class 中的 ServiceLocator
。有没有一种方法可以注入 IConnectionProvider
而不必更改所有派生 classes 以在其构造函数中包含 IConnectionProvider
并调用 : base(connectionProvider)
?我无法更改 DatabaseScopeManager
。
还有一个替代方案:属性 注入。
顺便说一句,属性 注入 没有定义强制依赖项,而构造函数依赖项定义了它。
正如 Matías Fidemraizer 所提到的,您可以使用 属性 注入,这是避免破坏性更改的一种非常安全的方法。缺点是 optional 中的 属性 依赖项,但在您的代码中 IConnectionProvider
是 a must.
从长远的 运行 角度来看,我会选择两个构造函数:
- 默认的 仍然 使用服务定位器(为了向后兼容)但被标记为
[Obsolete]
以指示它将在下一个中删除主要版本。 IConnectionProvider
作为 must-have 依赖项的构造函数。应该推荐使用这个,一旦所有默认构造函数的用法都被淘汰,应该会成为唯一一个。
这里问题的核心是你的基地的存在class。 你应该摆脱底座 class。 移除底座 class 将完全解决你的问题。
基数 class 有问题,因为:
- 它导致每个派生类型都依赖于此 class。
- 它使测试复杂化,因为所有 cross-cutting 问题总是被执行。
- 基地class将成为一个不断增长的大class;单一责任违规。
虽然您可以使用 属性 注入来摆脱服务定位器,但这并没有消除所述问题,甚至引入了一个新问题,即:Temporal Coupling.
相反,您应该简单地摆脱基础 class 并将这些 cross-cutting 问题移到装饰器中。对于您的情况,我建议创建两个装饰器。
一个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;
}
}
还有一个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
}
}
这使得AccountCreateBusiness
可以写成如下:
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
}
}
用装饰器包装每个处理程序对于大多数 DI 容器来说是微不足道的,并且当手动执行此操作时(a.k.a。纯 DI)。
是最后的设计建议。捕获每个异常并将此信息作为消息的一部分 return 通常不是一个好主意,因为:
- 这些错误代码正在泄漏到与其无关的系统基础(您的业务层)。不要在基本响应中提供此信息,而是使用包含此信息的信封。
- 您将向客户提供技术信息。客户通常对这种详细程度不感兴趣。您只想 return 功能消息。
- 安全敏感信息已 returned;黑客喜欢这种信息。
- 如果您不记录此信息,就会丢失很多重要的错误详细信息。
- 基本上 return 错误代码,而不是处理异常。错误代码很容易出错;客户端需要经常检查结果以查看响应是否成功。