【问题标题】:Dependency injection: single class (WCF service) having multiple dependencies (DB repositories) how to handle?依赖注入:单个类(WCF服务)有多个依赖(数据库存储库)如何处理?
【发布时间】:2014-08-11 19:24:11
【问题描述】:

我读过 Mark Seemann 的书"Dependency injection in .NET",它让我对许多事情大开眼界。但还剩下几个问题。这是其中之一:

假设我们有一个 WCF 服务公开 API 用于处理某些数据库:

public class MyService : IMyService
{
    private ITableARepository _reposA;
    private ITableARepository _reposB;
    //....

    public IEnumerable<EntityA> GetAEntities()
    {
        return _reposA.GetAll().Select(x=>x.ToDTO())
    }

    public IEnumerable<EntityB> GetBEntities()
    {
        return _reposB.GetAll().Select(x=>x.ToDTO())
    }

    //...
}

可能有几十个服务依赖的存储库。有些方法使用一种,有些方法使用另一种,有些方法使用很少的存储库。

我的问题是如何正确地将存储库依赖项注入到服务中

我看到的选项:

  1. 构造函数注入。创建一个带有几十个参数的巨大构造函数。易于使用,但难以管理参数列表。此外,这对性能非常不利,因为每个未使用的存储库都是浪费资源,即使它不使用单独的数据库连接。
  2. 属性注入。优化性能,但使用变得不明显。服务的创建者应该如何知道要为特定方法调用初始化哪些属性?此外,这个创建者应该对每个方法调用都是通用的,并且位于组合根中。所以那里的逻辑变得非常复杂且容易出错。
  3. 有点不标准(书中没有描述)的方法:创建一个存储库工厂并依赖它而不是具体的存储库。但是这本书说,工厂经常被错误地用作克服问题的一种方法,这些问题可以通过正确使用 DI 得到更好的解决。所以这种方法对我来说看起来很可疑(同时实现了性能和透明度目标)。

或者这种关系 1 与许多依赖关系是否存在概念问题?

我认为答案应该根据服务实例上下文模式而有所不同(可能是单实例时,构造函数注入就可以了;对于 PerCall 选项 3,如果忽略上述警告看起来最好;对于 perSession,一切都取决于会话生命周期:无论是更接近单实例还是 PerCall)。

如果它真的依赖于实例上下文模式,那么就很难对其进行更改,因为更改需要对代码进行大量更改(从构造函数注入转移到属性注入或存储库工厂)。但是 WCF 服务的整个概念确保更改实例上下文模式很简单(而且我需要更改它的可能性不大)。这让我对 DI 和 WCF 的组合更加困惑。

谁能解释这个案例应该如何正确解决?

【问题讨论】:

  • 你错了 1. 在性能和参数数量方面blog.ploeh.dk/2010/01/20/…
  • @WiktorZychla,很好的论据。尽管如果我们需要 PerCall 实例上下文模式,并遵循应尽快关闭 SQL 连接的建议,我们最终会在每次需要时创建数十个新存储库。我曾经看到一个有 200 多个表的数据库。幸运的是,存储库的创建成本并不高。但是从设计的角度来看是不是很奇怪?
  • 不是,我不在乎创建了多少实例,只要你正确关闭所有打开的连接。

标签: c# dependency-injection


【解决方案1】:

用几十个参数创建一个巨大的构造函数

您不应该创建具有大量构造函数参数的类。这是constructor over-injection 代码气味。具有大量参数的构造函数表明该类做得太多:违反了Single Responsibility Principle。这导致代码难以维护和扩展。

此外,由于每个未使用的存储库都浪费资源,因此对性能非常不利

你测量过这个吗?构造函数参数的数量应该主要与应用程序的性能无关。这不应该导致任何明显的性能差异。如果确实如此,那么是时候看看你的构造函数所做的工作量了(因为injection constructors should be simple),或者如果你的构造函数很简单,是时候切换到更快的 DI 容器了。创建一堆服务类通常应该非常快。

即使它不使用单独的数据库连接。

构造函数首先不应该打开连接。再说一遍:他们应该是simple

属性注入。优化性能 服务的创建者应该如何知道为特定的方法调用初始化哪些属性

调用者无法可靠地确定需要哪些依赖项,因为通常只需要构造函数参数。要求属性会导致 temporal coupling 并且您将失去编译时支持。

由于调用者无法确定需要哪些属性,因此需要注入所有属性,这使得性能与构造函数注入相同,正如我所说的那样,这根本不成问题。

有点不标准(书中没有描述)的方法:创建一个存储库工厂并依赖它而不是具体的存储库。

您可以注入一个存储库提供者,而不是注入存储库工厂,这种模式更为人所知的是Unit of Work pattern。工作单元可以授予对存储库的访问权限。

我认为答案应该因服务实例上下文模式而异

不,因为您不应该使用 WCF 的“单一”模式。在大多数情况下,您注入 WCF 服务的依赖项不是线程安全的,并且不应超过单个请求。将它们注入单例 WCF 服务会导致 Captive Dependencies 这很糟糕,因为它会导致各种并发错误。

这里的核心问题似乎是您的 WCF 服务类很大并且违反了单一职责原则,导致它们难以创建、维护和测试。通过以下任一方式解决此违规问题:

  1. 将它们分成多个较小的类,或
  2. 将功能从它们移到aggregate services 并应用command/handlerquery/handler 等模式。

【讨论】:

  • 我知道你是对的,但我不敢相信。一些疑问仍然在警告我。你能澄清两点吗? 1. 你和 Wiktor 投票支持构造函数注入,但同时你警告构造函数过度注入。结合拆分 WCF 服务的建议,这是否意味着我应该为每个存储库提供单独的服务?这看起来像一个糟糕的设计,难以维护和部署。查看 Amazon EC2 API (docs.aws.amazon.com/AWSEC2/latest/APIReference/…) - 它在单个 WCF 服务中包含约 150 个方法 - 这是否违反 SRP?我相信没有。
  • @OleksandrPshenychnyy:Amazon API 参考是否告诉您有关实际类结构的任何信息?你怎么这么确定这 150 个方法在同一个类中?
  • 2.使 WCF 服务实现命令/处理程序模式很少是自然的。我目前的项目中有一个,但其余的都分别为每个查询使用强类型 API 方法。您不认为这种重新设计是为了适应具体的实现或模式 (DI) 而改变设计吗?
  • @OleksandrPshenychnyy:不,CommandProcessor 不应该注入所有 500 个命令处理程序,它应该进行调度。以blog post 中的QueryProcessor 实现为例。它让您很好地了解如何解决这个问题。
  • 哇!你改变了我的想法!我以前听说过并阅读过 CQRS,但从不同的角度(可靠性和可扩展性)理解了这一点。现在可能我会在不久的将来尝试这个。非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-12-19
  • 1970-01-01
  • 1970-01-01
  • 2018-05-02
  • 1970-01-01
  • 1970-01-01
  • 2011-09-11
相关资源
最近更新 更多