【问题标题】:Common Service Registry公共服务登记处
【发布时间】:2011-06-04 17:50:30
【问题描述】:

很长一段时间以来,我们有幸拥有通用服务定位器 (CSL) 来解析来自未知来源的服务。但是,从一开始就没有任何与容器无关的解决方案来注册这些服务。我们一直面临着必须将我们的组合代码耦合到特定 IoC 容器的问题,此外,还要致力于在整个应用程序中使用该容器。

我觉得我可能在这里尝试实现不可能的目标,但是有人对如何实现 Common Service Registry (CSR) 有任何想法吗?

我最初的想法是使用MEF来解析各种IContainerIntegrators(每种容器技术一个类),然后使用MEF来解析各种IXXXContainerBindings(每种技术一个接口)。然后,用户可以围绕容器绑定开发应用程序,只需将其绑定放入 BIN 目录即可实现插件架构。如果他们想使用新的容器技术,那么他们只需要开发一个新的IContainerIntegrator 类和随附的IXXXContainerBinding,然后他们将使用它来编写自己的绑定。在应用程序启动时,CSR 使用 ServiceLocatorAggregator 类将所有容器实例聚合到一个 CSL 中。

我已经完成了这项工作,但面临以下问题:

  • 任何调用当前(不完整)容器的绑定都是不稳定的,因为之前的注册可能被后续的绑定覆盖。这包括需要解析对象以做出注册决定的绑定(即“如果存在则绑定它”)。
  • 可能存在两个公开同一组服务的绑定。但是,我们希望“交错”这些注册。例如。 '我想要来自绑定 X 的服务 A 和 C,以及来自绑定 Y 的服务 B 和 D'。
  • 在自动连接所请求服务的依赖项时,容器会自行解析服务。例如。 'container.Bind.To()' 将自动注入从 'container' 解析的服务的实现,而不是来自聚合服务定位器。

如果我完全错过了这里的要点,请对我大喊大叫,但这不是解耦的依赖管理解决方案吗?会不会很好?我即将开始一个带有插件架构的大型企业项目。我不想提交到特定的 IoC 容器。

(p.s. 这个问题是关于支持发现的与容器无关的组合。请不要就 SL 与 DI 进行辩论。SL 用于组合,这就是为什么我在这里大量引用它的原因)。

【问题讨论】:

  • 我认为不依赖于一个特定的 IoC 容器是有意义的。但是同时使用其中的几个?对我来说,这听起来是个坏主意。
  • 我们还有哪些其他选择?我不认为我们可以两者兼得。 Application Vendor 可能使用 Autofac,但 Plugin Vender A 可能使用 Ninject, Plugin Vendor B 可能使用 Castle。
  • 我认为插件内部使用什么并不重要,只要您的插件 API 直接提供所有必需的。
  • 所以回答Dependency Inject (DI) “friendly” library 可能对你有用。
  • @svick - 不确定我是否理解你的意思。绑定(或插件)可以用新的实现替换现有的实现。例如。我们的应用程序可能依赖于 ISearchProvider。默认绑定可能会向 LuceneProvider 注册,但稍后会发布一个新的绑定来注册 GoogleProvider。我的意思是,这两个绑定不应该使用相同的容器技术……但目前它们确实如此。

标签: c# .net dependency-injection inversion-of-control ioc-container


【解决方案1】:

您可以实现的最解耦(也是最好的)解决方案是认识到松散耦合最好通过原则和模式而不是特定技术来实现。

在整个应用程序中使用构造函数注入。这可确保您的应用程序层根本不需要引用任何容器。然后compose the entire application graph in the root of the application

需要为此使用 DI 容器,但如果您选择使用 DI 容器,则必须将其隔离到 Composition Root。这意味着如果您以后决定迁移到不同的容器,您只需要更改 Composition Root。但是,Poor Man's DI 也是一种选择。

【讨论】:

  • 如何使组合根可扩展,以便可以在不重新编译应用程序的情况下对其进行修改?如何允许两个用户编写自己的插件,这些插件包含在组合根目录中?如果这些插件的作者决定使用容器来处理生命周期等复杂性怎么办?我已经看到你在之前的问题中多次使用这个确切的答案,但它没有回答我的问题。我想要一个模块化和可扩展的组合根
  • 使用支持 XML 配置的 DI Container(大部分都支持)。
  • 这种方法会将应用程序与特定的 DI 容器耦合。最初的问题提出了一个与容器无关的解决方案。
  • 它将应用程序与容器耦合,但不是插件。这不是你想要的吗?
  • 是的,我明白了。您是否阅读了整个讨论?它几乎解释了您必须处理的权衡。您要么提供发现机制,要么提供显式配置选项(例如在 XML 中)。发现机制可以基于 MEF 之类的属性,或者可能基于约定(例如,如果有明确的接口实现者,则会自动使用它)。在应用程序启动时,您可以简单地扫描目录中的所有程序集并将所有接口实现注册到应用程序的容器中。
【解决方案2】:

您可以使用许多技巧来避免在大部分代码中依赖特定的 IoC 容器,其中最简单的方法是使用构造函数注入。如果您对使用 Service Locator 模式一无所知,只需创建自己的 Service Locator 类,它会包装您计划使用的实际 IoC 容器内核。

也就是说,IoC 容器的目的是实现“控制反转”:即将控制从底层移动到顶层。这意味着您需要在应用程序的“顶部”(或“根”)附近有一个点,该点实际上知道它将使用的所有服务实现,以及您的特定 IoC 实现。这应该仅限于少数几个类。通常,应用程序的“上下文根”是您将初始化 IoC 容器和服务定位器的地方。应该有一个特定的模块或一组模块负责设置所有绑定。

如果你想允许插件,你需要创建一个特定的 API 供他们使用和遵守。简单地允许其他包随意定义新的 IoC 绑定会导致灾难,因为您不知道这些不同的包将如何协同工作。

ASP.NET MVC 3 就是一个很好的例子。它们具有您在 Global Application_Start 方法中覆盖的特定服务工厂定位器。为了实现这些工厂之一,您必须遵守它们为您提供的 API。但是您可以创建一个使用任何您想要的 IoC 容器的实现,或者根本不使用。你根本没有改变“绑定”。您只是告诉框架,对于当前应用程序,您希望它使用“这个工厂”来创建控制器或模型元数据提供者,而不是使用默认工厂。

要使用另一个更适用于您的具体示例的示例,让我们以ISearchProvider 为例。你可能有一个内置的LuceneProvider,也许你的一个插件可以提供一个GoogleProvider。您想使用这些提供商中的哪一个? GoogleProviderPlugin 的存在是否意味着 LuceneProvider 不再可用?搜索是否应该以某种方式结合这两个提供者的结果?用户是否可以从用户界面中选择一个或多个提供者?

不管这些问题的答案如何,最终的一点是您希望您的应用程序来控制它,而不是插件。与其让插件 carte blanche 来处理你的 DI 绑定,不如告诉插件,“我允许你定义额外的搜索提供程序,这里是你如何注册它们的方法。”它们可以通过多种方式注册,包括类注释/属性或仅存在实现给定接口的类。但重要的一点是,有一个 API 专门定义了它们可以“插入”的内容,以及您对构建插件的任何人的要求。

现在,如果 GoogleProvider 具有在插件中定义的依赖项,则该插件可以根据需要解决这些依赖项。希望它会使用某种 IoC 容器,但如果不使用,那就不是你的皮肤了。您仍然可以不知道他们使用的容器类型(如果有的话)。

如果您希望 SearchProvider 需要某些服务,您可以包含这些服务或这些服务的工厂,作为插件初始化 API 的一部分。这样,您的插件就可以访问这些服务,而无需了解应用程序的 IoC 容器。

【讨论】:

  • 再次,不确定我的问题是否被理解。我完全了解构造函数 DI 并在任何地方使用它。然而,随着这种模式而来的是组合根——即类被实例化的地方。我要解决的问题是如何创建一个灵活的组合根,无需重新编译应用程序即可轻松扩展。最明显的解决方案是使用 MEF 来发现容器绑定。但是,我想要一个与容器无关的解决方案。
  • 郑重声明,我并不执着于使用 SL;这是不可避免的。容器内部使用 SL。您认为这些依赖项是如何注入的?容器在内部定位它们。 Service Location用于实现依赖注入;它们是对模式的补充。 DI 不是一种可以完全隔离使用的模式。但是,是的,我同意,有些人确实不恰当地使用 SL,但这里不是这种情况。
  • @Lawrence:我更新的最后一段是否让我对插件的观点更加清晰? MVC 不要求用户绑定到任何特定的 IoC 容器,但它们仍然使用户可以轻松地决定哪些实现用于他们希望用户能够定义的服务。
  • @StriplingWarrior:本质上,MVC 暴露了它自己的容器供你填充工厂。这是一种方法,但每次需要新的实现时都需要修改应用程序的代码。这不是插件架构。
  • @Lawrence:“容器在内部使用 SL”... 有点,但不完全。当人们谈论服务定位器时,他们通常在谈论您可以从中请求服务实现的静态属性或单例。 Ninject 使用“内核”,将它们的绑定封装在一个实例中。我不能说“Ninject,给我一个 X”。我不得不说,“_kernel,给我 X。”如果你通过静态属性使你的主要 Ninject 内核可用,并且有各种类向它请求服务,那就是人们不赞成的“服务位置”。
猜你喜欢
  • 2011-03-24
  • 2013-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-30
  • 2016-02-04
  • 2013-09-02
  • 2020-01-18
相关资源
最近更新 更多