Retro Installing an IOC container in the Brownfield Enterprise.net app

We have a very large and complex enterprise application that started working in 2005 before IOC containers were widespread in .NET. We would like to modify the IOC container as part of our migration to a full event driven architecture based on RabbitMQ (and easynetq). As a business, we agree, this will give us a commercial edge over our competitors.

I feel it is important to give some preamble as the implementation strategy is key:

  • 1.5 million + lines of C # using .net 4, in 600+ projects, 30,000+ classes, 40,000+ Unit Tests deployed to over 50 different application endpoints (exe command line, windows services, web services, etc.) etc.).
  • Simple CRUD application size is less than 10%. Most of the application is complex transactional processing where a single input value can easily traverse 20-30 dependencies that can talk to 2 or 3 other subsystems and / or modules.
  • We ship a major new release once a month to all customers with all different configurations and the app is very stable, but we are still actively innovating. We must migrate over a period of 12 to 36 months.
  • Scalability and performance are very important to us. We tested a load of over 1,500 transactions per second and a response time of 80ms. Every millisecond is counted.
  • A very stable and fairly consistent architecture that is constantly evolving in a controlled estate. The development is pretty painless.

At the moment all dependency injection is constructor and hand based:

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;
        }
    }

      

A unit of work contains access to repositories that can be easily mocked:

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
}

      

At the moment, all test dependencies are conditionally compiled in debug mode. The release code uses non-confidential code where we create a specific dependency. The largest object dependency is UnitOfWork, which is usually created around each business transaction and passed down the stack. For example.

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

      

This is usually wrapped up inside another class that also supports IDisposable. We are also planning to pass in AccountID to UnitOfWork so that we can easily penalize against different databases by moving forward using the mod function.

To get to the dependency structure it seems like we need to sort the UnitOfWork first, but we need baby steps. I'm really looking for advice on the best way to achieve this with such a large app. We plan to do phase 1 during the Xmas period, where we have a nice freeze and merge in all desperate branches into one mainline branch to make big changes.

We are open on what basis the dependency framework should be used. We had a little game with StructureMap. We can see that Ninject has high download stats on Nuget, but it seems to have a poor performance rating. We really don't want the further migration process to be another means of dependency injection. Therefore, we are open to suggestions. This is not a religious war, which is best, all the more important that we have to migrate something. The most important requirement is to smoothly configure it to avoid the configuration of hell.

Other problems we have in terms of StructureMap is how we declare registries. Do we declare registries for each build? Any recommended standard for naming around folders, class names in a large application? My rough guess is around 1000 registries. Also, any ideas on scanning strategies for such a large codebase? Should we be worried?

Thanks for your willingness, but the background is important as this is not a 10 minute job.

Hubert

+3


source to share


1 answer


Pursuing this as an answer, but there is no correct alternative to your problem, just to overcome the limitation of comments.

You have many advantages in your scenario to implement IoC:

  • Your classes are already decoupled and prepared for constructor injection;
  • You share your dependencies with conditional flags.


So, I'll just point out a few tips based on your current situation.

  • If performance is critical, be careful when using Ninject. As a heavy user of Ninject, I find it extremely flexible and powerful due to its Free Configuration, Modules and Contextual Links. But all this power comes with a price, and its on-request performance is much worse compared to other IoC containers.

  • Regardless of the selection structure, and since you start making these changes, you don't want to be tied to a container. Make sure you render it and then you can switch on the fly for another.

  • You already have your own configuration "modules". Make sure you collect them in modules when you include the container configuration code. Don't leave all dependencies for all modules configured in one place. Ninject supports modules, but it's very easy to achieve with any container, especially if you render them. Don't use the same module names for dummy / real implementations, but load one module or another based on your configuration.

  • According to "auto register" or "by convention" you have to be very strict and careful about the standard, otherwise it is very easy to mess up. I highly recommend not to use aggressive autoregister advance requests and keep them in minimal and very simple cases, such as "IAccountRepository" → "AccountRepository" or "AccountRepositoryImpl". And even those, you can have modulo conventions separated so that you can override them for testing purposes.

  • Make small changes usually during the release cycle, do not make changes in the branch or keep them there until you integrate them. As you said, baby steps. You already have addiction injection, so it will be painless. Enforce this policy internally to use the container and make small changes as features are implemented or rewritten.

Here are my 2 cents, hope it helps.

+2


source







All Articles