【问题标题】:IoC ArchitectureIoC 架构
【发布时间】:2016-06-17 14:09:08
【问题描述】:

我最近一直在阅读 Mark Seemann 关于依赖注入的书,它提出了一些关于控制反转的架构问题。 假设我有以下内容:

  • 一个非常基本的可执行项目,用作组合根,称为 CompositionRoot.exe。
  • 编译为库的域项目,称为 Domain.dll
  • 编译为库的数据访问项目,称为 DAL.dll
  • 编译为库的日志记录项目,称为 Logging.dll

按照 Seeman 书中的 IoC 模式,存储库接口在 Domain 中定义。 DAL 引用域并实现这些接口。 CompositionRoot 负责实例化这些存储库并将它们注入到域中。到目前为止,如此平静。

现在是问题;日志记录如何适应这种情况?

我曾设想过域和 DAL 都使用日志库。一些关于 StackOverflow 的阅读表明,一些开发人员认为日志记录只属于域。有时登录 DAL 对我很有用,例如在对特定 SQL 片段进行基准测试时,或者在不将实体框架特定异常暴露给域的情况下记录实体框架异常。

假设我想同时登录域和 DAL(除非有人能说服我否则)。日志接口是否应该在类似于存储库接口的域中定义?如果是这样,这会将 DAL 的日志记录绑定到域日志记录,这感觉是错误的。或者,DAL 可以定义自己的日志接口,Logging 也实现了该接口。然而,这导致 Logging 必须为每个需要记录的新 DLL 实现一个新接口,这也让人感觉不对。或者,Domain 和 DAL 是否应该参考 Logging(似乎违反 IoC)?还有其他方法吗?我还没有读完 Seemann 的书,但有几点他提到了使用接口库,即只包含接口的 DLL。我目前无法想象这是如何工作的。

【问题讨论】:

    标签: logging dependency-injection inversion-of-control


    【解决方案1】:

    这一切都由Dependency Inversion Principle (DIP)(驱动依赖注入的原理)完成。 DIP 指出:

    摘要归上层/政策层所有

    换句话说:抽象应该由使用它的层定义。因此,如果您的域层需要日志记录,那么在域层中具有日志记录抽象似乎是显而易见的。

    但这并不意味着您的域层应该依赖于您的日志记录层/库。如果您使用外部库(或使您的日志库可重用),它不可能依赖于您的域层,因为显然它是不可重用的。然而,DIP 将我们引导到具有核心层且不依赖外部库的应用程序。相反,对外部库的依赖应该一直向上移动到合成根。组合根依赖于all the application assemblies。因为您的域层和日志库不能相互依赖,所以解决方案是在组合根中实现一个适配器。此适配器将实现 Domain.ILogger 抽象,其 Log 方法将调用您的 Logging 库的日志记录功能。

    请注意,此建议并非晦涩难懂的做法。这实际上是将核心层与其他部分分离的方法,Robert C. Martin 和 Alister Cockburn 等人多年来一直在解释。 Robert C. Martin 将这种架构称为Screaming Architecture,而Alister Cockburn 使用术语Hexagonal Architecture。几年前,Mark Seemann explained 两种架构都是一样的。

    关于您的另一个问题,DAL 是否应该使用域层的日志记录抽象?

    由于 DAL 已经与域层耦合(因为存储库抽象是在域中定义的),因此让 DAL 层也依赖于日志记录抽象也就不足为奇了。您唯一需要认真考虑的是这是否会违反Liskov Substitution Principle。换句话说,消费者(DAL 和域)是否对这种抽象有相同的期望?如果不是这种情况,让 DAL 拥有自己的日志抽象并(再次)在组合根中拥有一个将调用转发到日志库的适配器是有意义的。这使得将 DAL 日志写入具有不同详细级别的不同源变得非常容易。 DAL 记录器的界面也可能有所不同,因为您似乎特别希望在那里进行性能测量。这一切都与域无关,因为依赖关系是从 DAL 到域,而不是相反。

    【讨论】:

    • 嗨史蒂文,感谢您深思熟虑的回复。在阅读这篇文章之前,由于使用了第三方记录器,我已经熟悉了您编写的所有内容,并且确实按照您所描述的方式编写了一个测试应用程序。然而,由于域和 DAL 之间共享日志记录,我仍然感到惊愕。你提到了 LSP,我才终于明白了这种不和谐的根源。您的帖子帮助澄清了我的想法,因此我将其标记为已接受的答案。谢谢。
    【解决方案2】:

    日志记录更像是基础架构,因此将由您的所有组件共享。为什么它必须存在于当前定义的项目之一中,例如 Domain?除非您正在编写日志库,否则我不希望它出现在 Domain 项目中。

    你的日志是静态的还是注入一些日志接口? 听起来你想使用日志接口,所以这就是我要做的。

    创建一个库,其中包含您的日志接口并在域和 DAL(以及您要登录的任何其他项目)中引用该接口。然后为您的日志记录创建一个实现库并在您的控制台应用程序中引用它。使用 DI 将您的实现注入到您的域/DAL/任何想要记录的类中。

    不过,老实说,我只会使用内置的 TraceSource 类,或 ETW 或许多现有的日志库之一,静态地覆盖所有内容并完成它。务实求胜!

    【讨论】:

    • 明确地说,我没有把我的日志实现放在域中,我把它放在一个独立的库中。我打算将记录器注入到任何需要它的类的构造函数中(在域和 DAL 中)。
    • 关于创建接口库并从域和 DAL 引用它的建议,这将具有不与具体实现紧密耦合的好处。有什么缺点吗?与接口库紧密耦合?
    • 听起来你是在正确的轨道上。拥有一个单独的接口库的缺点是它可能没有必要,除非您要拥有多个记录器实现,但老实说,除非您有充分的理由,否则我只会使用框架内置的内容。
    【解决方案3】:

    您希望在依赖注入模式和抽象实现方面走多远?你还会注入排序、内存分配和线程创建等基本功能吗?

    使用Domain Driven DesignDependency Injection 模式,您可以让实现者灵活地提供一个或多个适合模型的实现并隔离每个组件的职责。日志记录是任何程序的标准操作,但如果您不对一种日志记录机制进行标准化,组件实现者最终可能会选择不同的日志记录框架。

    我建议您为项目建立一个标准的日志记录机制(即使它是 stdout + stderr),并将此决定记录在例如根自述文件。有许多语言和框架都内置了标准的日志记录机制,该机制的可配置性足以让您依赖它。例如,它允许某种上下文感知,以便该上下文中的日志可以配置为忽略、信息、错误等。由于目前问题中没有指定语言,我不能提出任何具体的建议。

    【讨论】:

      猜你喜欢
      • 2012-11-13
      • 2023-04-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-25
      • 2012-07-05
      • 1970-01-01
      • 1970-01-01
      • 2011-04-12
      相关资源
      最近更新 更多