Retro将IOC容器装入Brownfield Enterprise .net应用程序

时间:2014-10-30 14:50:48

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

我们有一个非常庞大,复杂的企业应用程序,在IOC容器在.NET中广泛使用之前于2005年开始。我们希望改进IOC容器,作为迁移到基于RabbitMQ(和easynetq)的完整事件驱动架构的一部分。作为一家企业,我们同意这将为我们提供超越竞争对手的商业优势。

我觉得提供一些序言非常重要,因为实施策略很关键:

  • 使用.net 4的150多万行C#,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函数轻松地对不同的数据库进行Shard。

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

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

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

感谢您准备好这一点,但背景很重要,因为它不是一个10分钟的工作。

休伯特

1 个答案:

答案 0 :(得分:2)

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

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

  1. 您的类已经解耦并为构造函数注入做好准备;
  2. 使用条件标记拆分依赖项。
  3. 所以,我会根据你目前的情况指出几点建议。

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

    • 无论选择何种框架,并且您开始介绍这些更改,您都不希望被绑定到容器。确保你抽象它,然后你就可以开启另一个。

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

    • 根据“自动注册”或“按惯例”,您应该非常严格并注意标准,否则很容易搞砸。我强烈建议不要使用激进的前期自动注册查询,并将它们保持在最小和非常简单的情况下,例如“IAccountRepository” - > “AccountRepository”或“AccountRepositoryImpl”。甚至那些,您可以按模块拆分约定,以便您可以覆盖它们以用于测试目的。

    • 在发布周期中正常进行小的更改,不要进行分支更改并将其保留在那里,直到您将它们集成为止。就像你说的,宝贝步骤。你已经有依赖注入,所以这将是无痛的。在团队中加强此策略以使用容器,并在实现或重新设计功能时进行小的更改。

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