【问题标题】:Dependency Injection in onion architecture for .NET Core.NET Core 洋葱架构中的依赖注入
【发布时间】:2020-04-02 14:14:04
【问题描述】:

我正在尝试在使用 EntityFramework 作为 ORM 的 .NET Core 3.1 Web API 项目中实现洋葱架构。

由于我刚刚学习洋葱架构,我对如何在某些领域遵循它的规则有一点问题。其中之一是依赖注入。

假设我们有以下环(从外到内):

  • 基础架构(API、持久性)
  • 应用程序(服务)
  • 域(模型 + 域服务)

根据我对洋葱架构的理解,同一层中的不同关注点不应相互依赖。因此,在基础设施环中,更具体地说,在 API 项目中,我了解如何为我的服务连接 DI,因为这些服务(接口和实现)位于较低的环中。但是,我还需要为我的 DbContext 设置 DI,它在同一个环的 Persistence 项目中定义。此外,同样存在于同一个环中的其他第三方工具也需要通过 DI 连接。

对此我有两种解决方案:

  1. 使 API 项目依赖于 Persistence 项目(以及其他第三方项目)。据我了解,这违反了洋葱架构规则。
  2. 将基础架构环分成两个环,其中下半部分将包含 Web API、持久性和任何其他项目,但上半部分将是专用的 DI 层,它将设置所有 DI。我相信这个外层称为组合根。

用另一种方式总结我的问题:在洋葱架构中,如果您使用 DI,那么 DI 看起来应该在基础架构环中,并且看起来 DI 需要引用基础架构环中的所有内容. DI 是否必须作为洋葱架构中的异常处理?如何处理?

【问题讨论】:

  • 请注意,不同的服务和层不必映射到单独的项目。并且该项目引用本身不会创建您试图避免的那种依赖关系。
  • @DavidBrowne-Microsoft:对于您的第一点,我同意。然而在我看来,如果代码/项目结构反映(或半反映)架构,它有助于提高可读性,并且对于新加入的人来说,它有助于他们了解事情应该去哪里。对于您的第二点,我有点困惑,因为如果 A 引用 B,那么人们可以使用 A 中 B 中的任何内容。我认为您的意思是引用项目不会创建直接依赖关系?你能详细说明你的意思吗?
  • 您可以通过代码审查以外的任何方式实施架构是一种幻想。即使您的代码没有直接使用被引用项目中的类型,直接引用项目进行编译或部署也可能很方便,有时甚至是必要的。
  • 一个常见的例子是您的主项目注册服务。最简单的解决方案是让主项目引用每个服务实现的类型。 EG services.AddScoped<ICustomerRepository, MySQLServerCustomerRepository>();,如果不引用包含存储库实现的程序集,则无法编译该代码。
  • 我认为你走极端了 :) 当然我知道我无法“强制执行”它,但为什么不根据架构从逻辑上构建项目呢?我们确实添加了诸如“.Data”或“.Services”之类的后缀,对吗?

标签: c# .net-core dependency-injection architecture onion-architecture


【解决方案1】:

这是我的理解:

在编译时,域不依赖于自身之外的任何东西。因此,例如,域将不依赖于持久性项目。这将取决于域本身内定义的抽象。

您的持久性(例如,具体的存储库)将满足域中定义的依赖关系。在现实世界中,这通常意味着他们将实现域中定义的接口。

组合根然后配置应用程序,以便具体实现与域中定义的抽象相匹配。这就是DI配置。

这是最简单的版本。如果需要,您可以添加更多复杂性。唯一不会改变的部分是域是自包含的。以下是复杂性可能增加的其他一些方式如果需要

  • 依赖项的实现也可以是自包含的,就像域一样。还有一个项目使它们适应域接口。因此,您的存储库不会直接实现域接口。
  • 域抽象的配置——将它们映射到它们的具体依赖关系——可以分开。换句话说,您的容器设置可以与应用程序主机分开。理想情况下,应用程序主机将继续负责读取其配置或环境值并将它们传递给该容器配置。这可以防止容器配置与 JSON 或 .config 文件等细节耦合。更容易测试。

但关键的细节是,在任何情况下,域本身都是独立的。我们不会将定义在域外的接口注入到域类中。域定义自己的抽象,然后在域外定义的类实现这些抽象。


您的 API 可以依赖于持久性项目。您的 API 不是域。您的 API 启动将依赖于域和持久性,并配置 DI 容器以提供持久性项目中的类来满足域中定义的依赖关系。

API 就像域的对立面。域不依赖于自身之外的任何东西。其他依赖项指向内部。例如,存储库实现由域定义的抽象。

另一方面,API 最终取决于一切。它使用 DI 配置向域提供依赖项(如持久性),这意味着它将依赖于所有这些。


这是(在我看来)思想上最大的变化:

我们倾向于为数据访问编写具体的类、调用外部 API 以及做其他与领域无关的事情。然后我们在它们上面放置接口。这些界面通常看起来像是事后才想到的。就像我们只是在创建反映这些类的接口。如果我们在类中添加一些东西,我们会将其添加到界面中。

然后我们使用这些接口并开始将它们注入到我们的域中。这就是事情变得混乱的地方。这些接口与我们的领域无关。它们只是域外存在的类的镜像。它使域更难测试。违反了接口隔离。我们发现自己在模拟与我们的领域无关的接口部分。

如果我们从依赖于它们的领域类的角度来设计我们的抽象,那么所有这些都会消失。例如,如果我们的域类需要从存储库中检索数据,我们定义一个存储库,该存储库准确地建模了该域类所需的内容——不多也不少。我们不会采用一些通用的通用存储库接口并将其塞进我们的域中。

我们可能还有一些通用的通用存储库。没关系。但是我们将其调整为该域存储库接口。域对此一无所知。它所知道的只是域中定义的抽象。域类依赖于它为自己的目的定义的小的、隔离的抽象。它拥有它们。这使它变得简单,并且非常容易测试。

【讨论】:

  • 我同意你所说的大部分内容。当您专注于域层时,还有应用层也具有相同的约束(除了它可以依赖于域,而域不能依赖于任何东西)。但是,如果您从概念上思考,为什么 API 需要访问持久性细节? API 只是使用应用层服务的端点。
  • API 只需要在 API 启动时配置(向域提供持久性)的范围内访问持久性类。如果该配置在其他地方(您指的是应用层吗?),那么 API 将仅依赖于此。
  • API 只需要访问 DTO。应用服务只使用 DTO,这些 DTO 是在应用环上定义的。我猜 API 可以访问域模型,但我不这样做。我也没有两个不同版本的模型,我只有域模型,它们在域环中。持久性项目具有 DbContext 类和任何其他 EF 配置调整
  • API 将依赖于一切。域类在 API 的上下文中执行。为了让 API 执行任何操作,它必须调用域类。一种看待它的方式:域可以在没有 API 的情况下工作吗?是的。您可以运行单元测试。您不需要 API。 API 可以在没有域的情况下工作吗?不。如果没有域,它会如何处理 DTO?
  • 术语“有权访问”可能没有帮助。 (我也用过。)谈论某事所依赖的东西可能更有帮助。域可以在不依赖于自身之外的任何东西的情况下进行编译。为了实际执行,必须有其抽象的实现。当我想到洋葱架构时,我想到的是编译代码需要什么。域可以在没有任何抽象实现的情况下进行编译,但它需要它们实际执行。
【解决方案2】:

这是我解决问题的方法。我认为它很干净,尽管我必须测试更多的场景/用例才能安全地采用这个解决方案。

除了我的 API 项目,我还创建了一个 DI 项目。此 DI 项目是组合根项目。它依赖于一切(应用程序、域等)它是基础设施环的上部(外部子环)。然后,在我的 API 项目中,我引用了 DI 项目并从那里调用扩展方法以将我的所有合同与实现联系起来。此外,作为同一解决方案的一部分,我必须为其他东西添加第二个 Web 应用程序,并且我能够使用相同的方法,这很好。

【讨论】:

    猜你喜欢
    • 2013-01-31
    • 2011-12-08
    • 2015-05-19
    • 2020-03-23
    • 2017-11-23
    • 1970-01-01
    • 2014-01-25
    • 2011-10-09
    • 2022-09-24
    相关资源
    最近更新 更多