国际奥委会架构
IoC Architecture
我最近一直在阅读 Mark Seemann 关于依赖注入的书,它提出了一些关于控制反转的架构问题。
假设我有以下内容:
- 一个非常基本的可执行项目,充当组合根,称为 CompositionRoot.exe。
- 编译为库的域项目,名为 Domain.dll
- 编译为库的数据访问项目,名为 DAL.dll
- 编译为库的日志记录项目,名为 Logging.dll
遵循 Seeman 书中的 IoC 模式,存储库接口在域中定义。 DAL 引用域并实现这些接口。 CompositionRoot 负责实例化这些存储库并将它们注入到域中。到目前为止,还很平静。
现在是问题;日志记录如何适应这种情况?
我曾设想域和 DAL 都使用日志记录库。 Whosebug 上的一些阅读表明一些开发人员认为日志记录只属于领域。有时登录 DAL 对我很有用,例如在对 SQL 的特定部分进行基准测试时,或者在不向域公开 Entity Framework 特定异常的情况下记录 Entity Framework 异常。
然后假设我想同时登录域和 DAL(除非有人可以说服我)。日志接口是否应该像存储库接口一样在域中定义?如果是这样,这会将 DAL 的日志记录与域日志记录联系起来,这感觉不对。或者,DAL 可以定义自己的日志记录接口,Logging 也实现了该接口。然而,这导致 Logging 必须为每个需要日志记录的新 DLL 实现一个新接口,这也让人感觉不对。或者,Domain 和 DAL 是否应该参考 Logging(似乎违背 IoC)?还有别的办法吗?我还没有读完 Seemann 的书,但他在几个地方提到了使用接口库,即仅由接口组成的 DLL。我目前无法想象这是如何工作的。
日志记录更像是基础设施,因此将由您的所有组件共享。为什么它必须存在于当前定义的项目之一中,例如 Domain?除非您正在编写一个日志记录库,否则我不希望它出现在 Domain 项目中。
您的日志记录是静态的还是注入一些日志记录接口?
听起来您想使用日志记录界面,所以这就是我要做的。
创建一个包含您的日志记录接口的库,并在域和 DAL(以及您要登录的任何其他项目)中引用它。然后为您的日志记录创建一个实现库,并在您的控制台应用程序中引用它。使用 DI 将你的实现注入你想要记录的 Domain/DAL/Whatever 类。
老实说,我只是使用内置的 TraceSource classes, or ETW 或许多现有的日志库之一静态地跨所有内容并完成它。务实求胜!
这一切都完成了 Dependency Inversion Principle (DIP)(驱动依赖注入的原则)。 DIP 状态:
the abstracts are owned by the upper/policy layers
换句话说:抽象应该由使用它的层定义。因此,如果您的域层需要日志记录,那么在域层内进行日志记录抽象似乎很明显。
但这并不意味着您的域层应该取决于您的日志记录 layer/library。如果您使用外部库(或使您的日志记录库可重用),它不可能依赖于您的域层,因为它显然不可重用。然而,DIP 引导我们开发具有核心层且不依赖于外部库的应用程序。相反,对外部库的依赖应该一直移动到 Composition Root。 Composition Root 依赖于 all the application assemblies。因为您的域层和日志记录库不能相互依赖,所以解决方案是在组合根内部实现一个适配器。此适配器将实现 Domain.ILogger
抽象,其 Log
方法将调用 Logging
库的日志记录功能。
请注意,此建议并非晦涩难懂的做法。这实际上是将核心层与其他部分分离的方法,Robert C. Martin 和 Alister Cockburn 等人多年来一直在解释。几年前,Robert C. Martin 称这种架构 Screaming Architecture and Alister Cockburn uses the term Hexagonal Architecture. Mark Seemann explained 两种架构都是一样的。
关于你的另一个问题,DAL 是否应该使用域层的日志记录抽象?
由于 DAL 已经耦合到域层(因为存储库抽象是在域中定义的),所以让 DAL 层也依赖于日志抽象也就不足为奇了。您唯一需要认真考虑的是这是否会违反 Liskov Substitution Principle。换句话说,两个消费者(DAL 和域)是否对该抽象有相同的期望?如果不是这种情况,让 DAL 拥有自己的日志记录抽象并(再次)在组合根中有一个适配器将调用转发到日志记录库是有意义的。这使得将 DAL 日志写入具有不同详细级别的不同来源变得非常容易。 DAL 记录器的接口也可能有所不同,因为您似乎特别希望在那里进行性能测量。这一切都与域无关,因为依赖关系是从 DAL 到域,而不是相反。
您希望依赖注入模式和抽象实现走多远?您还会注入排序、内存分配和线程创建等基本功能吗?
使用 Domain Driven Design and Dependency Injection 模式,您可以让实施者灵活地提供一个或多个适合模型的实施并隔离每个组件的职责。日志记录是任何程序的标准操作,但如果您不对一种日志记录机制进行标准化,组件实现者可能最终会选择不同的日志记录框架。
我建议您为项目建立一个标准的日志记录机制(即使它是 stdout + stderr),并将此决定记录在例如根自述文件。有许多语言和框架内置了标准的日志记录机制,该机制具有足够的可配置性,您可以依赖它。例如,它允许某种上下文感知,以便该上下文中的日志可以配置为忽略、信息、错误等。因为此时问题中没有指定语言,所以我不能提出任何具体建议。
我最近一直在阅读 Mark Seemann 关于依赖注入的书,它提出了一些关于控制反转的架构问题。 假设我有以下内容:
- 一个非常基本的可执行项目,充当组合根,称为 CompositionRoot.exe。
- 编译为库的域项目,名为 Domain.dll
- 编译为库的数据访问项目,名为 DAL.dll
- 编译为库的日志记录项目,名为 Logging.dll
遵循 Seeman 书中的 IoC 模式,存储库接口在域中定义。 DAL 引用域并实现这些接口。 CompositionRoot 负责实例化这些存储库并将它们注入到域中。到目前为止,还很平静。
现在是问题;日志记录如何适应这种情况?
我曾设想域和 DAL 都使用日志记录库。 Whosebug 上的一些阅读表明一些开发人员认为日志记录只属于领域。有时登录 DAL 对我很有用,例如在对 SQL 的特定部分进行基准测试时,或者在不向域公开 Entity Framework 特定异常的情况下记录 Entity Framework 异常。
然后假设我想同时登录域和 DAL(除非有人可以说服我)。日志接口是否应该像存储库接口一样在域中定义?如果是这样,这会将 DAL 的日志记录与域日志记录联系起来,这感觉不对。或者,DAL 可以定义自己的日志记录接口,Logging 也实现了该接口。然而,这导致 Logging 必须为每个需要日志记录的新 DLL 实现一个新接口,这也让人感觉不对。或者,Domain 和 DAL 是否应该参考 Logging(似乎违背 IoC)?还有别的办法吗?我还没有读完 Seemann 的书,但他在几个地方提到了使用接口库,即仅由接口组成的 DLL。我目前无法想象这是如何工作的。
日志记录更像是基础设施,因此将由您的所有组件共享。为什么它必须存在于当前定义的项目之一中,例如 Domain?除非您正在编写一个日志记录库,否则我不希望它出现在 Domain 项目中。
您的日志记录是静态的还是注入一些日志记录接口? 听起来您想使用日志记录界面,所以这就是我要做的。
创建一个包含您的日志记录接口的库,并在域和 DAL(以及您要登录的任何其他项目)中引用它。然后为您的日志记录创建一个实现库,并在您的控制台应用程序中引用它。使用 DI 将你的实现注入你想要记录的 Domain/DAL/Whatever 类。
老实说,我只是使用内置的 TraceSource classes, or ETW 或许多现有的日志库之一静态地跨所有内容并完成它。务实求胜!
这一切都完成了 Dependency Inversion Principle (DIP)(驱动依赖注入的原则)。 DIP 状态:
the abstracts are owned by the upper/policy layers
换句话说:抽象应该由使用它的层定义。因此,如果您的域层需要日志记录,那么在域层内进行日志记录抽象似乎很明显。
但这并不意味着您的域层应该取决于您的日志记录 layer/library。如果您使用外部库(或使您的日志记录库可重用),它不可能依赖于您的域层,因为它显然不可重用。然而,DIP 引导我们开发具有核心层且不依赖于外部库的应用程序。相反,对外部库的依赖应该一直移动到 Composition Root。 Composition Root 依赖于 all the application assemblies。因为您的域层和日志记录库不能相互依赖,所以解决方案是在组合根内部实现一个适配器。此适配器将实现 Domain.ILogger
抽象,其 Log
方法将调用 Logging
库的日志记录功能。
请注意,此建议并非晦涩难懂的做法。这实际上是将核心层与其他部分分离的方法,Robert C. Martin 和 Alister Cockburn 等人多年来一直在解释。几年前,Robert C. Martin 称这种架构 Screaming Architecture and Alister Cockburn uses the term Hexagonal Architecture. Mark Seemann explained 两种架构都是一样的。
关于你的另一个问题,DAL 是否应该使用域层的日志记录抽象?
由于 DAL 已经耦合到域层(因为存储库抽象是在域中定义的),所以让 DAL 层也依赖于日志抽象也就不足为奇了。您唯一需要认真考虑的是这是否会违反 Liskov Substitution Principle。换句话说,两个消费者(DAL 和域)是否对该抽象有相同的期望?如果不是这种情况,让 DAL 拥有自己的日志记录抽象并(再次)在组合根中有一个适配器将调用转发到日志记录库是有意义的。这使得将 DAL 日志写入具有不同详细级别的不同来源变得非常容易。 DAL 记录器的接口也可能有所不同,因为您似乎特别希望在那里进行性能测量。这一切都与域无关,因为依赖关系是从 DAL 到域,而不是相反。
您希望依赖注入模式和抽象实现走多远?您还会注入排序、内存分配和线程创建等基本功能吗?
使用 Domain Driven Design and Dependency Injection 模式,您可以让实施者灵活地提供一个或多个适合模型的实施并隔离每个组件的职责。日志记录是任何程序的标准操作,但如果您不对一种日志记录机制进行标准化,组件实现者可能最终会选择不同的日志记录框架。
我建议您为项目建立一个标准的日志记录机制(即使它是 stdout + stderr),并将此决定记录在例如根自述文件。有许多语言和框架内置了标准的日志记录机制,该机制具有足够的可配置性,您可以依赖它。例如,它允许某种上下文感知,以便该上下文中的日志可以配置为忽略、信息、错误等。因为此时问题中没有指定语言,所以我不能提出任何具体建议。