洋葱与 N 层架构
Onion vs. N-Layered Architecture
事先一件事:我来自N层背景。
我现在花了很多时间了解洋葱架构和相关领域驱动概念,例如六角架构阅读资源,例如 Jeff Palermo's series of blog posts, Mark Seemann's contribution from a DI-perspective, "Onion-izing your achitecture", and "The clean architecture"。
所有这些文章的共同点是它们声称以下几点:
- 重点放在业务用例的领域模型上
- 通过强调依赖倒置原则来松散层之间的耦合
- 增加外部基础设施的独立性,例如框架、数据持久性,UI
- 更好的可测试性/可维护性
嗯,这一切听起来都非常好,而且那些图表看起来也很不错。但对我来说出现的问题是:所有这些不都是通过向我的传统 N 层架构添加外观来实现的吗?
- 每一层只知道下一层的抽象
- 具体实现可以保留在每一层内部,因此与抽象位于同一位置
- 实现细节可以很容易地换出,因为它们在层内部,不应该影响应用程序的其余部分
请帮助我了解以领域为中心的架构的真正优势。
提前致谢!
添加外观实际上是从 n 层架构构建洋葱架构的第一步。所以,是的,您可以立即获得许多好处。
测试仍然存在问题,因为您需要反转依赖控制。控制外观所指向的内容需要移交给消费者,而不是提供者。这允许消费者交换东西进行测试,或者更改实现而无需提供者知道它。
我和你的观点相同,我们正计划启动一个新项目,我的一位同事建议使用洋葱架构,但在记录了一些相关信息后,我有点困惑,因为(对我来说!)每个客户端层必须仅依赖于所用层的抽象这一事实是最佳实践的问题,无论我们计划使用什么架构,都必须始终牢记这一点,DI 是 "Principle"不是架构,所以我无法意识到如何将 N 层架构与 "good OO Principles" 一起使用使其成为一个新架构?
通过这种方式,我们将通过将所有 OO 原则和 Go4 模式组合到企业应用程序架构中,以数百种新架构结束。
直接回答您的问题"Isn't all of that achieved by merely adding façades to my traditional N-layered architecture?"。答案是肯定的,也不是,具体取决于您的用例。
Onion 架构的重点在于使用依赖倒置,正如您所说的..."create looser coupling"。然而,这不仅仅是为了失败者耦合。将其视为 "protecting the parts of your code that are least likely to change, from parts that are more likely to change" 可能会有所帮助。那么,对于您的情况,更改 "below" 外观是否需要更改您的 "domain" 代码?例如,对数据库对象的更改是否会渗透到外观中使用的对象的更改中,然后进入您的 "domain" 代码?如果这些类型问题的答案是 "no",那么您的假设是正确的,该代码没有有意义的功能差异。如果有人回答 "maybe",那么他们可能会受益于从外观到 IOC 的重构。
虽然是个很老的问题,但还是想补充一下。
这篇文章清楚地解释了为什么分层是不可取的,但如果你正确地实现了 IOC,它不会有任何不同。
我发现洋葱架构和分层架构的主要区别在于抽象的位置。当我想到分层架构时,我经常看到的模式是接口和实现紧挨着彼此。因此,假设您在 MyApp.DAL/Personnel 中有一个 IPersonAccessor 接口(我来自 C#),那么您将有一个相应的 PersonAccessor class 来实现 IPersonAccessor。这一切都很棒,因为它允许您在测试中切换实现,但它并没有真正进一步解耦它。
洋葱架构(也许这只是我自己对洋葱架构的解释)允许我们不仅class从依赖中解耦,而且层与层之间解耦。考虑一下这个场景,假设您想要构建 Web 项目的桌面版本,但使用文件存储而不是数据库存储。您将如何使用分层架构来做到这一点?
在这两种情况下,您都必须创建新版本的 DAL。但是,您将如何在业务层中考虑这两个不同的层?在分层架构中,抽象位于 DAL 中,因此为了让业务层能够编译,它必须包含对抽象所在层的引用。因此,您不能因为需要具有抽象的 DAL 就换掉 DAL。而且你不能只复制新 DAL 中编译语言的接口,因为这样(除了代码重复)只是命名相同的东西并不能使编译器相同。
解决方案是将抽象移动到使用它的层。您会将抽象移动到业务层。因此,在上面的示例中,您将拥有 MyApp.BL/Personnel/IPersonAccessor.cs
然后您将拥有 MyApp.DAL.DB/Personnel/PersonAccessor.cs
以及 MyApp.DAL.FileStorage/Personnel/PersonAccessor.cs
每个 DAL classes 将实现 IPersonAccessor 和您的 IOC 容器(其中最有可能存在于您的表示层中),因为您的应用程序可以注入它想要的任何 PersonAccessor。这样,您的 DAL 将 reference/depend 在业务层上,以便它的实现实现存在于业务层中的抽象。现在,就依赖关系而言,业务层实际上可以完全隔离存在。然后,您可以简单地通过在文件存储 DAL 而不是数据库 DAL 中注入实现来换出 DAL,并且业务层可以保持不变,因为两个 DAL 的 classes 使用完全相同的接口......那些存在于业务层。
有了这个新模式,你会得到一个奇怪的、陌生的图像。 DAL 依赖于业务层。这就是架构模式被认为是洋葱的原因。 DAL 本质上是外层的另一部分。它与表示层存在于同一层。事实上,我认为这与其说是 DAL 层和表示层……不如说是 "infrastructure" 层。在基础设施层内,您有数据访问代码、表示代码和其他与外界通信的代码。然后,在这一层之下,甚至不知道外界的存在,就是您的业务层。这是一个洋葱。一层被另一层多层保护免受外界影响projects/assemblies,使其看起来像洋葱。
所以,洋葱架构实际上是它自己的架构模式,因为你有不同的层,你的接口在不同的地方......或者换句话说,它们的结构不同,根据定义,这是一种架构模式。
在思考这个架构之后,我开始意识到这真的把你的业务层变成了一个可以在应用程序中进出的库,因为它除了日期时间库之类的实用程序库之外没有任何依赖关系(就像在 python 中一样)。这使您的应用程序非常动态,因为您可以轻松地执行更改平台、更改为微服务,然后可能更改回单体应用程序等操作。是的,无论如何这都不是一个可能的场景,但对于大型应用程序来说,这可能非常方便。您所要做的就是重写您的基础设施逻辑,但您的核心业务逻辑保持不变。
我曾经使用 N 层架构工作过一段时间,但大约一年前,我们的团队决定将重点转移到 Onion 架构上。说实话,从一开始就觉得很复杂。我们有完全相同的问题,因为每个人都有关系 between/inside 层级、职责等
互联网上有大量的文档,但是,如果您尽可能接近核心原则使用洋葱,那么在一天结束时您会感觉不那么痛苦。
以下是我们决定遵循的核心原则(规则):
- 核心(也称为域)应用于任何关系inside/between层
- 服务之间的关系(也称为用例)是允许的,但input/output合同应该在核心中定义(将其与其他接口区分开来并使用特定的业务逻辑接口)
- 基础设施没有业务逻辑,应该与所使用的应用程序无关(例如,如果你在那里有记录器工厂,你应该能够将它复制到不同的微服务中w/o任何调整都已完成)
- 数据访问必须通过存储库完成并放置在基础设施层中。然而,存储库是域和数据模型之间的一种适配器。
- 在核心中定义数据访问接口,以便至少指定输入参数
- onion完全独立于编程方式。它适用于 类 并且功能也很好。
- onion 在您的应用程序基于域时开始工作。每当你解耦逻辑时,都要从领域的角度而不是从功能的角度来看它
- 在测试中使用与源中相同的目录结构
- 在整个系统中使用相同的图层名称。例如。核心层应该在所有地方都具有完全相同的名称(适用于微服务)
到目前为止,我已经注意到 N 层架构和 Onion 之间的区别,您可以在一个地方放置合同,它确实可以帮助您遵循 Open/Close 原则。由于核心层不应该经常更改(理想情况下根本不应该更改),它可以帮助您在代码审查期间关注它。
我创建了一个基于 Onion 的简单脚手架项目。看一看,有什么不明白的地方随时问:)
事先一件事:我来自N层背景。
我现在花了很多时间了解洋葱架构和相关领域驱动概念,例如六角架构阅读资源,例如 Jeff Palermo's series of blog posts, Mark Seemann's contribution from a DI-perspective, "Onion-izing your achitecture", and "The clean architecture"。
所有这些文章的共同点是它们声称以下几点:
- 重点放在业务用例的领域模型上
- 通过强调依赖倒置原则来松散层之间的耦合
- 增加外部基础设施的独立性,例如框架、数据持久性,UI
- 更好的可测试性/可维护性
嗯,这一切听起来都非常好,而且那些图表看起来也很不错。但对我来说出现的问题是:所有这些不都是通过向我的传统 N 层架构添加外观来实现的吗?
- 每一层只知道下一层的抽象
- 具体实现可以保留在每一层内部,因此与抽象位于同一位置
- 实现细节可以很容易地换出,因为它们在层内部,不应该影响应用程序的其余部分
请帮助我了解以领域为中心的架构的真正优势。
提前致谢!
添加外观实际上是从 n 层架构构建洋葱架构的第一步。所以,是的,您可以立即获得许多好处。
测试仍然存在问题,因为您需要反转依赖控制。控制外观所指向的内容需要移交给消费者,而不是提供者。这允许消费者交换东西进行测试,或者更改实现而无需提供者知道它。
我和你的观点相同,我们正计划启动一个新项目,我的一位同事建议使用洋葱架构,但在记录了一些相关信息后,我有点困惑,因为(对我来说!)每个客户端层必须仅依赖于所用层的抽象这一事实是最佳实践的问题,无论我们计划使用什么架构,都必须始终牢记这一点,DI 是 "Principle"不是架构,所以我无法意识到如何将 N 层架构与 "good OO Principles" 一起使用使其成为一个新架构?
通过这种方式,我们将通过将所有 OO 原则和 Go4 模式组合到企业应用程序架构中,以数百种新架构结束。
直接回答您的问题"Isn't all of that achieved by merely adding façades to my traditional N-layered architecture?"。答案是肯定的,也不是,具体取决于您的用例。
Onion 架构的重点在于使用依赖倒置,正如您所说的..."create looser coupling"。然而,这不仅仅是为了失败者耦合。将其视为 "protecting the parts of your code that are least likely to change, from parts that are more likely to change" 可能会有所帮助。那么,对于您的情况,更改 "below" 外观是否需要更改您的 "domain" 代码?例如,对数据库对象的更改是否会渗透到外观中使用的对象的更改中,然后进入您的 "domain" 代码?如果这些类型问题的答案是 "no",那么您的假设是正确的,该代码没有有意义的功能差异。如果有人回答 "maybe",那么他们可能会受益于从外观到 IOC 的重构。
虽然是个很老的问题,但还是想补充一下。
这篇文章清楚地解释了为什么分层是不可取的,但如果你正确地实现了 IOC,它不会有任何不同。
我发现洋葱架构和分层架构的主要区别在于抽象的位置。当我想到分层架构时,我经常看到的模式是接口和实现紧挨着彼此。因此,假设您在 MyApp.DAL/Personnel 中有一个 IPersonAccessor 接口(我来自 C#),那么您将有一个相应的 PersonAccessor class 来实现 IPersonAccessor。这一切都很棒,因为它允许您在测试中切换实现,但它并没有真正进一步解耦它。
洋葱架构(也许这只是我自己对洋葱架构的解释)允许我们不仅class从依赖中解耦,而且层与层之间解耦。考虑一下这个场景,假设您想要构建 Web 项目的桌面版本,但使用文件存储而不是数据库存储。您将如何使用分层架构来做到这一点?
在这两种情况下,您都必须创建新版本的 DAL。但是,您将如何在业务层中考虑这两个不同的层?在分层架构中,抽象位于 DAL 中,因此为了让业务层能够编译,它必须包含对抽象所在层的引用。因此,您不能因为需要具有抽象的 DAL 就换掉 DAL。而且你不能只复制新 DAL 中编译语言的接口,因为这样(除了代码重复)只是命名相同的东西并不能使编译器相同。
解决方案是将抽象移动到使用它的层。您会将抽象移动到业务层。因此,在上面的示例中,您将拥有 MyApp.BL/Personnel/IPersonAccessor.cs
然后您将拥有 MyApp.DAL.DB/Personnel/PersonAccessor.cs
以及 MyApp.DAL.FileStorage/Personnel/PersonAccessor.cs
每个 DAL classes 将实现 IPersonAccessor 和您的 IOC 容器(其中最有可能存在于您的表示层中),因为您的应用程序可以注入它想要的任何 PersonAccessor。这样,您的 DAL 将 reference/depend 在业务层上,以便它的实现实现存在于业务层中的抽象。现在,就依赖关系而言,业务层实际上可以完全隔离存在。然后,您可以简单地通过在文件存储 DAL 而不是数据库 DAL 中注入实现来换出 DAL,并且业务层可以保持不变,因为两个 DAL 的 classes 使用完全相同的接口......那些存在于业务层。
有了这个新模式,你会得到一个奇怪的、陌生的图像。 DAL 依赖于业务层。这就是架构模式被认为是洋葱的原因。 DAL 本质上是外层的另一部分。它与表示层存在于同一层。事实上,我认为这与其说是 DAL 层和表示层……不如说是 "infrastructure" 层。在基础设施层内,您有数据访问代码、表示代码和其他与外界通信的代码。然后,在这一层之下,甚至不知道外界的存在,就是您的业务层。这是一个洋葱。一层被另一层多层保护免受外界影响projects/assemblies,使其看起来像洋葱。
所以,洋葱架构实际上是它自己的架构模式,因为你有不同的层,你的接口在不同的地方......或者换句话说,它们的结构不同,根据定义,这是一种架构模式。
在思考这个架构之后,我开始意识到这真的把你的业务层变成了一个可以在应用程序中进出的库,因为它除了日期时间库之类的实用程序库之外没有任何依赖关系(就像在 python 中一样)。这使您的应用程序非常动态,因为您可以轻松地执行更改平台、更改为微服务,然后可能更改回单体应用程序等操作。是的,无论如何这都不是一个可能的场景,但对于大型应用程序来说,这可能非常方便。您所要做的就是重写您的基础设施逻辑,但您的核心业务逻辑保持不变。
我曾经使用 N 层架构工作过一段时间,但大约一年前,我们的团队决定将重点转移到 Onion 架构上。说实话,从一开始就觉得很复杂。我们有完全相同的问题,因为每个人都有关系 between/inside 层级、职责等
互联网上有大量的文档,但是,如果您尽可能接近核心原则使用洋葱,那么在一天结束时您会感觉不那么痛苦。
以下是我们决定遵循的核心原则(规则):
- 核心(也称为域)应用于任何关系inside/between层
- 服务之间的关系(也称为用例)是允许的,但input/output合同应该在核心中定义(将其与其他接口区分开来并使用特定的业务逻辑接口)
- 基础设施没有业务逻辑,应该与所使用的应用程序无关(例如,如果你在那里有记录器工厂,你应该能够将它复制到不同的微服务中w/o任何调整都已完成)
- 数据访问必须通过存储库完成并放置在基础设施层中。然而,存储库是域和数据模型之间的一种适配器。
- 在核心中定义数据访问接口,以便至少指定输入参数
- onion完全独立于编程方式。它适用于 类 并且功能也很好。
- onion 在您的应用程序基于域时开始工作。每当你解耦逻辑时,都要从领域的角度而不是从功能的角度来看它
- 在测试中使用与源中相同的目录结构
- 在整个系统中使用相同的图层名称。例如。核心层应该在所有地方都具有完全相同的名称(适用于微服务)
到目前为止,我已经注意到 N 层架构和 Onion 之间的区别,您可以在一个地方放置合同,它确实可以帮助您遵循 Open/Close 原则。由于核心层不应该经常更改(理想情况下根本不应该更改),它可以帮助您在代码审查期间关注它。
我创建了一个基于 Onion 的简单脚手架项目。看一看,有什么不明白的地方随时问:)