.NET Core洋葱体系结构中的依赖注入

时间:2020-04-02 14:14:04

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

我试图在以EntityFramework作为ORM的.NET Core 3.1 Web API项目中实现洋葱体系结构。

由于我只是在学习洋葱架构,因此在某些领域如何遵循其规则存在一些问题。其中之一是依赖注入。

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

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

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

对此我有两种解决方案:

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

以不同的方式总结我的问题:在洋葱体系结构中,如果您使用的是DI,则DI看起来应该位于Infrastructure环中,并且DI似乎需要引用Infrastructure环中的所有内容。 。 DI是否必须作为洋葱体系结构中的例外处理?该如何处理?

2 个答案:

答案 0 :(得分:2)

这是我的理解:

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

您的持久性(例如,具体的存储库)将满足域中定义的依赖项。用现实世界的术语来说,通常意味着它们将实现域中定义的接口。

然后,合成根对应用程序进行配置,以使具体实现与域中定义的抽象相匹配。那就是DI配置。

那是最简单的版本。如果需要,可以增加更多复杂性。唯一不会更改的部分是域是独立的。这是如果需要可以增加复杂性的其他方法:

  • 依赖关系的实现也可以是独立的,例如域。另一个项目使它们适应域接口。因此,您的存储库不会直接实现域接口。
  • 域抽象的配置-将它们映射到它们的具体依赖项-可以分开。换句话说,您的容器设置可以与应用程序主机分离。理想情况下,应用程序主机将负责读取其配置或环境值,并将其传递给该容器配置。这样可以避免将容器配置耦合到JSON或.config文件等详细信息。测试起来更容易。

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


您的API可以依赖于持久性项目。您的API不是域。您的API启动将取决于域和持久性,并配置DI容器以提供来自持久性项目的类,以实现域中定义的依赖项。

API类似于域的对立面。域本身不依赖任何内容。其他依赖关系指向内。例如,存储库实现了由域定义的抽象。

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


(我认为)这是思想上最大的改变:

我们倾向于编写用于访问数据的具体类,调用外部API以及进行域外围的其他操作。然后我们在它们上放置接口。这些接口通常看起来像事后的想法。就像我们只是在创建反映那些类的接口一样。如果我们将某些内容添加到类中,则会将其添加到界面中。

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

如果我们从依赖抽象类的领域类的角度来设计抽象,所有这些都将消失。例如,如果我们的域类需要从存储库中检索数据,则可以定义一个存储库,该存储库可以准确地建模该域类的需求-仅此而已。我们不会采用一些通用的通用存储库接口并将其塞入我们的域中。

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

答案 1 :(得分:0)

这是我解决问题的方式。我认为这很干净,尽管在安全地采用此解决方案之前,我必须测试更多的方案/用例。

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