【问题标题】:Retro Fitting an IOC container to Brownfield Enterprise .net application将 IOC 容器复古安装到 Brownfield Enterprise .net 应用程序
【发布时间】:2014-10-30 14:50:48
【问题描述】:

我们有一个非常庞大、复杂的企业应用程序,它始于 2005 年,当时 IOC 容器还没有在 .NET 中广泛使用。我们希望改造一个 IOC 容器,作为我们迁移到基于 RabbitMQ(和 easynetq)的完整事件驱动架构的一部分。作为一家企业,我们同意这将使我们比竞争对手更具商业优势。

我觉得做一些序言很重要,因为实施策略是关键:

  • 150 万多行 C# 使用 .net 4,在 600 多个项目、30000 多个类、40000 多个单元测试中部署在 50 多个不同的应用程序端点(命令行 exe、Windows 服务、Web 服务等)中。李>
  • 应用程序具有的简单 CRUD 数量不到 10%。大多数应用程序都是复杂的事务处理,其中单个输入值可以轻松地通过 20 到 30 个依赖项,这些依赖项可能与 2 或 3 个其他子系统和/或模块通信。
  • 我们每月向所有具有各种不同配置的客户发布一个重要的新版本,该应用程序非常稳定,但我们仍在积极创新。我们需要迁移 12 到 36 个月。
  • 可扩展性和性能对我们来说非常重要。我们已经对每秒超过 1500 个事务和 80 毫秒的响应时间进行了负载测试。每一毫秒都很重要。
  • 非常稳定且相当一致的架构,在受控庄园中不断发展 - 开发相当轻松。

目前所有的依赖注入都是基于构造函数的并且是手动的:

public sealed class TestCommandHandler
{
        private readonly IUnitOfWork _unitOfWork;
        private readonly ITestCommandValidator _testCommandValidator;

        public TestCommandHandler(IUnitOfWork unitOfWork)
        {
            this._unitOfWork = unitOfWork;
            this._testCommandValidator = new ITestCommandValidator(unitOfWork);
        }

        public TestCommandHandler(IUnitOfWork unitOfWork, IValidator testCommandValidator)
        {
            this._unitOfWork = unitOfWork;
            this._testCommandValidator = testCommandValidator;
        }
    }

工作单元包含对可以轻松模拟的存储库的访问:

public class UnitOfWork : IUnitOfWork, IDisposable
{
        private IAccountRepository _accountRepository;

        public IAccountRepository Account
        {
            get
            {
                if(this._accountRepository == null)
                {
                    this._accountRepository = new AccountRepository(this);
                }
                return this._accountRepository;
            }
            set
            {
                this._accountRepository = value;
            }
        }
        //Begin Tx, Commit Tx etc
}

目前,所有测试依赖项都在调试模式下有条件地编译。发布代码使用我们创建具体依赖项的非条件代码。 最大的对象依赖是 UnitOfWork,它通常围绕每个业务事务创建并向下传递。例如,

using(var unitOfWork = new UnitOfWork())
{
}

这通常包装在另一个也支持 IDisposable 的类中。我们还计划将 AccountID 传递到 UnitOfWork 中,这样我们就可以使用 mod 函数轻松地对不同的数据库进行分片。

为了转移到依赖框架,感觉就像我们需要首先对 UnitOfWork 进行排序,但我们需要一个小步骤。我真的在寻找关于使用如此大的应用程序实现这一目标的最佳方法的建议。我们计划在圣诞节期间进行第一阶段,在此期间我们会很好地冻结并将所有绝望的分支合并到一个主线分支中以进行重大更改。

我们对使用哪个依赖注入框架持开放态度。我们对 StructureMap 进行了一些尝试。我们看到 Ninject 在 Nuget 上的下载统计数据很高,但读取它的性能得分很低。我们真的不希望进一步迁移到另一个依赖注入框架。所以我们愿意接受建议。这不是一场最好的宗教战争,更重要的是我们有一些东西可以迁移。最重要的要求是流畅地配置以避免配置地狱。

我们在 StructureMap 术语中的其他顾虑是我们如何声明注册表。我们是否声明每个程序集的注册表?在大型应用程序中,有什么推荐的围绕文件夹、类名的命名标准吗?我的粗略猜测是将有大约 1000 个注册表。另外,对于如此庞大的代码库的扫描策略有什么想法吗?我们应该担心吗?

感谢您到目前为止的准备,但背景很重要,因为这不是 10 分钟的工作。

休伯特

【问题讨论】:

  • 我也使用了 ninject 很长一段时间,并且喜欢它的界面。由于您需要性能,因此我会研究 Autofac。它在功能方面类似于 Ninject,但似乎表现更好。另见:palmmedia.de/blog/2011/8/30/…

标签: .net dependency-injection ninject inversion-of-control structuremap


【解决方案1】:

将此作为答案发布,但您的问题没有正确答案,只是为了克服评论限制。

为了实现 IoC,您在场景中有很多优势:

  1. 您的类已经解耦并准备好进行构造函数注入;
  2. 您使用条件标志拆分依赖项。

所以,我将根据您目前的情况提出几点建议。

  • 如果性能至关重要,请小心使用 Ninject。作为一个重度 Ninject 用户,我发现它的流畅配置、模块和上下文绑定非常灵活和强大。但所有这些功能都是有代价的,与其他 IoC 容器相比,它的每个激活请求的性能要差得多。

  • 无论选择何种框架,并且由于您开始引入这些更改,您都不想被绑定到容器。确保您将其抽象化,然后您可以随时切换到另一个。

  • 您已经按配置拆分了“模块”。确保在插入容器配置代码时将它们收集在模块中。不要将所有模块的所有依赖项都配置在一个地方。 Ninject 支持模块,但是使用任何容器都很容易实现这一点,特别是如果你抽象它们。不要为虚拟/真实实现使用相同的模块名称,而是根据您的配置加载一个或另一个模块。

  • 根据“自动注册”或“按惯例”,您要非常严格并注意标准,否则很容易搞砸。我强烈建议不要使用激进的预先自动注册查询并将它们保持在最低限度和非常简单的情况下,例如“IAccountRepository”->“AccountRepository”或“AccountRepositoryImpl”。即使是这些,您也可以按模块拆分约定,以便您可以覆盖它们以进行测试。

  • 在您的发布周期中正常进行小更改,不要进行分支更改并保留它们直到您集成它们。就像你说的,婴儿步。你已经有了依赖注入,所以这将是无痛的。在团队内加强此政策以使用容器并在功能实现或返工时进行小幅更改。

这是我的 2 美分,希望对您有所帮助。

【讨论】:

  • 我同意抽象依赖框架是个好主意。我们将其他所有内容抽象化,并且之前已经多次切换技术。团队在这方面经验丰富,每个人都以努力保持基于代码的组织为荣;否则,一个大泥球就可以了。这是我们改变的部分原因
猜你喜欢
  • 2019-07-21
  • 2013-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-30
  • 1970-01-01
相关资源
最近更新 更多