0

背景

我有许多由依赖于要创建的连接字符串的组件实现的服务 - 例如:

public interface IImportantRepository { ... }

public class ImportantRepository
{
    public ImportantRepository(IOracleConnection connection) { ... }
    public ImportantRepository(string connectionString) { ... } // rarely my constructor of choice, but included for clarity
}

我通常更喜欢第一个构造函数而不是第二个构造函数,因为为了使用连接字符串,我必须创建一个与它的连接,这反过来又需要解析IOracleConnection依赖string项。

MyIImportantController有一个构造函数和参数IImportantRepository。MyIControllerFactory是组合根(不包括引导程序),并IWindsorContainer.Resolve<T>()在运行时用于激活作业的正确控制器。

所以我想整个事情在引导时看起来像这样:

Global.Asax => ControllerBuilder.Current => MyControllerFactory : IControllerFactory

这在请求时:

IControllerFactory => IImportantController => IImportantRepository => IOracleConnection

动机

现在,在一个应用程序中,IOracleConnection(以及构建它的底层字符串)在以下情况下可能是已知的:

  • 应用程序被设计(用于测试的虚构连接字符串)
  • 应用程序已编译(完全由构建变量确定的连接字符串)
  • 应用程序已部署(转换中设置的连接字符串web.config
  • 应用程序被引导(在配置源中设置的连接字符串只读一次)
  • 应用程序处理某种类型的请求(例如,“通常”但不是“总是”您想要显示来自的数据ProductionDb
  • 应用程序处理一个连接被参数化的请求(一个管理Important东西的请求,ChicagoDb而不是NewYorkDb,或其他)
  • 应用程序处理一个请求,其中会话或用户详细信息完全决定了连接(10% 的用户继续使用Db1,其余用户Db2根据设计继续使用)

问题

您将如何编写可维护的代码而不过多地违反 DRY,从而同时为同一个图实现相同依赖项的所有这些潜在用途?

4

1 回答 1

0

基元与其他组件可能依赖的任何其他类型以及您可能在容器中注册的任何其他类型非常相似。它们可以被命名,它们可以从一些静态上下文(例如配置文件、环境变量或当前用户会话)中解析出来,等等。

您列出的场景都可以适用于复杂类型或原始类型,并且其中大多数都有干净的 DRY 解决方案。

然而,在处理原语时有几个挑战:

  1. 我认为将原始类型与容器中的复杂类型区分开来的一件事是目的(服务标识)的概念。就容器而言,“连接字符串”只是一个字符串。它不是连接字符串、IP 地址或主机名——它只是一个字符串。
  2. 撇开自定义解析器的可能性不谈,连接原语以满足其他组件的依赖要求的主要方法是在注册时命名原语,然后在注册依赖组件时引用该名称。但是,使用名称会产生通常不必要的耦合(至少在注册方面)。

因此,我宁愿从不注册或依赖原语。我总是将它们包装在一个界面中(通常以“...设置”结尾),如下所示:

public interface IMSSqlDatabaseConnectionSettings 
{
    string ConnectionString { get; set; }
}

public interface IOracleDatabaseConnectionSettings 
{
    string ConnectionString { get; set; }
}

这个相对简单的包装器现在为原语提供了其用途的指示。它有助于开发需要这些原语的组件,因为我现在可以通过组件构造函数清楚地传达我期望的连接类型(MSSQL Repository 组件根本不会接受到 Oracle 数据库的连接字符串)。它还有助于注册时非常清楚依赖关系是什么。

在实践中,大多数时候我将这些“设置”对象的构造卸载到Krzysztof Koźmic的优秀AppSettingWrapperAttribute的变体。(长话短说:它为配置文件的 AppSettings 中的设置字典提供了一个强类型包装器。)因此,我实际上很少编写这些类的实现,更不用说实例化它们了。但是,您当然可以这样做(例如,为测试提供硬编码字符串,或形式化传递给控制台应用程序的命令行参数)。

将原语包装在明确指示其用途的接口中也意味着在许多情况下您不需要使用名称进行注册。例如,如果只有一个“生产 Oracle 数据库”,那么任何需要 a 的组件IOracleProductionDatabaseConnectionSettings都会从容器中解析正确的设置,而无需任何魔术字符串。

于 2015-10-14T22:50:40.027 回答