除了实例化 DAL 的 BLL 之外,还有啥选项允许在 n 层解决方案中进行单元测试,而不会将 DAL 暴露给 UI 或将 BLL 暴露给 DAL?

Posted

技术标签:

【中文标题】除了实例化 DAL 的 BLL 之外,还有啥选项允许在 n 层解决方案中进行单元测试,而不会将 DAL 暴露给 UI 或将 BLL 暴露给 DAL?【英文标题】:What option other than a BLL instantiating a DAL allows for unit testing in an n-tier solution without exposing DAL to the UI or BLL to DAL?除了实例化 DAL 的 BLL 之外,还有什么选项允许在 n 层解决方案中进行单元测试,而不会将 DAL 暴露给 UI 或将 BLL 暴露给 DAL? 【发布时间】:2016-03-02 04:09:18 【问题描述】:

我有一个分层的解决方案如下:

UI(用户界面) BLL(业务逻辑层) DAL(数据访问层) SharedEntities(仅具有实体 POCO 的 VS 项目)

我希望 BLL 有一个名为 GetProductList() 的服务,该服务在我的 DAL 层中实现。我想过在 BLL 和 DAL 实现中定义一个接口如下:

选项 A:

// Interface defined in BLL 
public interface IDataServices

  List<Product> GetProductList();


// Interface implemented in DAL
public class DataServices : IDataServices

    Public List<Product> GetProductList()
    
      return //do some database work here and return List<Product>;
    

如果我想在 DAL 中实现它,那么我必须让 DAL 项目引用 BLL 项目才能看到 IDataServices 的接口定义。或者,我可以在 DAL 中复制接口定义,但最终我会使用重复的代码来维护(BLL 和 DAL 中的接口定义相同)。

选项 B: 我可以这样做的另一种方法是忘记接口的想法,只需在 UI 可以使用的 BLL 中进行以下具体的类和方法调用:

// Concrete class defined in the BLL
public class DataServices

    Public List<Product> GetProductList()
    
         DAL aDAL = new DAL();
         Return (aDAL.GetProductList());
    

这很容易,但是 BLL 看到了 DAL 并引用了它,但这真的是一件坏事吗?只要 BLL 不使用任何数据库对象(即数据源、连接字符串等)来满足请求,并且 DAL 符合匹配我在 BLL DataServices 类中定义的服务名称,这还不够吗?我听到的所有关于交换另一个数据库引擎的讨论仍然可以通过确保下一个 DAL 提供 BLL 在 DataServices 类中标识的相同服务来完成,例如 GetProductList()。在这个设置中,UI 仍然不知道任何关于 DAL 的信息,而 DAL 也不知道任何关于 BLL 的信息。如果我考虑使用依赖注入来避免在 BLL 中实例化 DAL,这将意味着在 UI 中实例化它以传递给 BLL。我不想这样做会让 UI 访问 DAL 方法。

选项 C: 我简短地看了一下 Unity Container,但该工具建议在入口点预先注册所有接口和具体类,这本来是 UI,这反过来又为 BLL 和 DAL 提供了 UI 可见性,这似乎更糟.我看到了使用 MEF 和 Unity 来解决入口点看到所有层的问题的参考,但也看到如果你这样做,你就不能真正对这样的配置进行单元测试。与选项 B 相比,似乎需要大量工作和复杂性。

选项 D: 我想到的另一个选择是在 BLL 和 DAL 之间创建一个外观层(另一个 VS 项目)。这似乎没有多大意义,除非我最终得到了很多与 BLL 无关的 DAL 方法,因此它们必须被隐藏;允许 DAL Facade 仅显示 BLL 需要的内容。如果我要换入另一个数据库,我仍然必须根据 BLL 的需要创建外观所需的方法,正如我在选项 B 中提到的那样。

因此,基于所有这些,我正在考虑选择选项 B,但我想在这里获得一些社区意见。满足以下条件我还能做什么:

1) 不让 UI 看到 DAL 2) 不让 DAL 看到 BLL 3) 该解决方案仍然允许对所有层进行单元测试

【问题讨论】:

【参考方案1】:

您的选项 A(BLL 中的接口,DAL 中的实现)+ 和 IoC 容器是最好的方法。

在设计复杂的软件解决方案时,您必须将其分成更小的部分。

其中一些部分对于解决方案至关重要。它们将是您开发软件的原因,而不仅仅是购买现有解决方案。你必须专注于他们。这些部分将很复杂且难以实施。你对此无能为力。它们必须尽可能好地实施。

也会有简单的部分。易于实现的功能,甚至可以购买。尝试尽可能少地制作这些零件。它们是必要的,但它们不是你被雇用的目的。

首先关注坚硬的部分。那里的业务逻辑会很复杂。业务逻辑将不依赖于您选择的存储解决方案 - 所以不要让它依赖于您系统中的 DAL 项目。在业务逻辑层中为实体的存储库定义接口(如果遵循 DDD,则为聚合)。

您应该能够彻底测试业务逻辑。如果 BLL 依赖于 DAL,则不能这样做。

UI 应该与业务逻辑完全分离。 UI 只是用户可以执行的一组用例。因此,它应该向用户提供有限的信息,并且只接受来自用户的有限信息。

要将 UI 与 BLL 分开,请使用单独的 ViewModel 和 Command 类来显示和接受来自用户的信息。在每个用例中使用额外的应用层来编排 BLL。

这种方法众所周知为Hexagonal architecture、Onion architecture 或Clean architecture。它也在领域驱动设计书籍中被引用。

当然,您需要一个将所有这些依赖项放在一起的地方。这个地方是composition root,它应该尽可能靠近应用程序入口点。如果您不想引用 UI 项目中的所有图层,请将合成根移动到另一个项目。

【讨论】:

我在一个 WinForms 应用程序的 main() 入口点尝试了一种组合根方法,但这让 UI 可以访问所有内容。我没有尝试创建一个单独的项目来放置它,所以我想我会尝试一下,让 UI 在启动时调用该组合根项目。不过,一个挑战是我有一个工作单元模式类来管理一个通用存储库,该存储库设置为支持 2 个实体。工作单元类实现了 IDisposable,所以如果它被释放,那么它的实例是无效的。这是我必须调查一个容器来处理的一件事。很多工作。 @Robertcode 将 UI(所有表单等)移动到另一个项目。将 Main 方法设为您的组合根。那么你的根是一个单独的项目——它是唯一引用所有其他项目的地方。其他项目严格遵循不向内依赖的六角/洋葱/清晰架构原则。 BLL 可以抽象地依赖于 DAL。它不依赖于具体的 DAL。出于这个原因,它也可以被测试,我注入一个模拟 DAL。你能给出一些为什么 BLL 甚至不能以抽象方式(例如通过接口)引用 DAL 的原因吗?我认为这也是一个合理的选择,但我不知道为什么会这样做。 我已经创建了另一个与此相关的问题,但重点是使用 Unity Container 来解决这个问题。链接在这里:***.com/questions/33976371/… 这里的原始问题将保留,因为它提供了一些关于在没有 Unity 的情况下实施的信息。 Unity 路径确实增加了复杂性。非 Unity 方法可能不那么优雅,但可能是一种更实用的方法。我还没有决定。 我的解决方案是使用纯依赖注入方法而不是容器或 BLL 看到 DAL 方法。我在 *** 上提出的与此页面相关的以下问题讨论了此解决方案:***.com/questions/33976371/…【参考方案2】:

IDataServices 应该在 DAL 中定义。 but then the BLL sees the DAL and has a reference to it 这是很自然的做法。一个项目可以引用它下面的层。如果您不允许向下引用,则根本就没有引用。

请注意,Reflection 引用仍然是一个引用,因为您不能在不更改上面的图层的情况下更改下面的图层。依赖项不是仅编译时的概念。选项 B 没有任何好处。它删除了编译时依赖,但不删除运行时依赖。

删除从 A 到 B 的依赖关系的要点是,您可以在不更改 A 的情况下更改 B。这就是重点。运行时依赖项仍然很重要。

关于 C:您可以让 UI 要求 BLL 注册其依赖项。然后 BLL 可以要求 DAL 注册。这样,UI 就不会受到 DAL 的影响。

D:我不知道这会实现什么。

通过定义 DAL IDataServices 可以轻松满足您的约束。

【讨论】:

BLL(或域模型)是最低层。它不应该依赖(参考)任何其他层。 BLL 中实现的业务逻辑足够复杂,可以避免基础设施层(DAL 等)出现任何技术复杂性 @JakubLortz 这是一个奇怪的模型。通常,BLL 位于 UI 和 DAL 之间。为什么 DAL(在您的模型中)会引用 BLL?为什么 UI 会引用 DAL?这就是你的意思。 我编辑它说共享实体只是 POCO 的一个项目,因此任何层都可以引用它进行数据布局。它没有任何操作。 @Robertcode 好的,所以?对我来说,SharedEntities 似乎对这个问题无关紧要。我的回答对你有帮助吗? 当您考虑数据的流动时,就是这样。但是业务逻辑层是复杂软件系统中最重要的一层,所以让它独立于基础设施(包括数据访问)是合理的。它可以通过使用依赖反转来完成 - 在 BLL 中定义所需的接口,这些接口稍后在 DAL 中实现。检查有关六边形架构或洋葱架构的信息,或任何 DDD 参考。

以上是关于除了实例化 DAL 的 BLL 之外,还有啥选项允许在 n 层解决方案中进行单元测试,而不会将 DAL 暴露给 UI 或将 BLL 暴露给 DAL?的主要内容,如果未能解决你的问题,请参考以下文章

C# |如何制作用于将数据从 DAL 传递到 BLL 的类实例(对象)

静态方法和实例化方法的区别 -转载

延迟加载的 DAL 和 BLL

在ASP.NET中,三层架构,Web ,BLL,DAL,Models这四个的引用关系是?

DAL 和 BLL 应通过的类型

不同的 UI 共享相同的 BLL 和 DAL