我在洋葱架构和分层架构中发现的主要区别是抽象的位置。当我想到分层架构时,我经常看到的模式是接口和实现紧挨着。因此,假设您在 MyApp.DAL/Personnel 中有一个 IPersonAccessor 接口(我来自 C#),那么您将有一个相应的实现 IPersonAccessor 的 PersonAccessor 类。这一切都很棒,因为它允许您在测试中切换实现,但它并没有真正将它进一步解耦。
洋葱架构(也许这只是我自己对洋葱架构的解释)让我们不仅可以将类与依赖解耦,还可以将层与层解耦。考虑一下这个场景,假设您想构建 Web 项目的桌面版本,但使用文件存储而不是数据库存储。您将如何使用分层架构来做到这一点?
在这两种情况下,您都必须创建 DAL 的新版本。但是,您将如何在业务层中解释这两个不同的层?在分层架构中,抽象位于 DAL 中,因此为了编译业务层,它必须包含对存在抽象层的引用。因此,您不能只换掉 DAL,因为您需要具有抽象的 DAL。而且你不能只是在新的 DAL 中为编译语言复制接口,因为(除了代码的重复)只是命名相同的东西并不能使它与编译器相同。
解决方案是将抽象移动到使用它的层。您会将抽象移动到业务层。所以在上面的例子中,你会拥有MyApp.BL/Personnel/IPersonAccessor.cs
,然后你会拥有MyApp.DAL.DB/Personnel/PersonAccessor.cs
以及MyApp.DAL.FileStorage/Personnel/PersonAccessor.cs
每个 DAL 类都将实现 IPersonAccessor 并且您的 IOC 容器(很可能存在于您的表示层中)为您的应用程序可以注入它想要的任何 PersonAccessor。这样,您的 DAL 将引用/依赖于业务层,以便它的实现实现业务层中存在的抽象。现在,就依赖关系而言,业务层实际上可以完全孤立地存在。然后,您可以简单地通过在文件存储 DAL 而不是数据库 DAL 中注入实现来交换 DAL,并且业务层可以保持不变,因为两个 DAL 的类使用相同的确切接口......业务中存在的接口层。
使用这种新模式,您会得到一个奇怪的、不熟悉的图像。DAL 依赖于业务层。这就是为什么架构模式被认为是洋葱。DAL 本质上是外层的另一部分。它与表示层存在于同一层。事实上,我认为这与其说是 DAL 层和表示层......我认为它是“基础设施”层。在基础设施层内,您拥有数据访问代码、表示代码和其他与外部世界通信的代码。然后,在这一层之下,甚至不知道外部世界的存在,是您的业务层。这是一个洋葱。一层由多个项目/组件的另一层保护免受外界影响,使其像洋葱一样。
所以,洋葱架构实际上是它自己的架构模式,因为你有不同的层,你的接口在不同的地方......或者换句话说,它们的结构不同,根据定义,这是一种架构模式。
在思考了这个架构之后,我开始意识到这真的把你的业务层变成了一个更多的库,它可以被扔进和扔出应用程序,因为它除了像 datetime 库这样的实用程序库(比如在 python 中)之外没有其他依赖项。这使您的应用程序极具动态性,因为您可以轻松地执行诸如更改平台、更改为微服务,然后可能更改回单体应用程序之类的事情。是的,无论如何这都不是一个可能的场景,但对于大型应用程序来说,这可能非常方便。您所要做的就是重写您的基础架构逻辑,但您的核心业务逻辑保持不变。