Dependency injection on the loose - Load different versions of components at runtime

Akos Nagy
Feb 16, 2017

So a while back I was presented with an interesting task.

It is not uncommon that if you have some code, it starts to evolve over time. You introduce changes — and with the changes a new version of your code is born. And this presents many challenges.

The challenge that I'm about to discuss now is simple: users sign up for version A, but your code is now version B. And they still want to use version A (for some reason), so you have to offer them the option to choose (personally I like to take a more totalitarian approach: show the users a dialog that says that the service has been updated and from now on they can use the new one or not use it at all; I would even go as far as disabling older client software).

And this is indeed a challenge. Let's say you have an ASP.NET Web API. You are a good software engineer and you know the basics of layering, so you don't write your business logic in the Web API project, but in separate classes in a separate class library. You might even go as far as putting an interface over your business logic. So you might have something like this:

public interface IMyService
{
  string MyBusinessAction();
}
public class MyServiceV1 : IMyService
{
  public string MyBusinessAction()
  {
    return "Original value";
  }
}
[RoutePrefix("api/Values")]
public class ValuesController : ApiController
{
  private readonly IMyService myService;

  public ValuesController()
  {
    this.myService = new MyServiceV1();
  }

  [HttpGet]
  [Route()]
  public IHttpActionResult Get()
  {
    return Ok(myService.MyBusinessAction());
  }
}

Well, kudos to you. You have the basics of software engineering down. Not perfect, but who is, right?

And then the unexpected happens: you need another version of IMyService — and you have to support both the old version and the new version. Let's say clients send an extra header parameter, Version, with every request with which they can indicate the required version of your services.

So what now? First, you cannot use the constructor of the controller to instantiate the service, because the HttpContext is not available to you. OK, you use the Initialize() method. I'm not really an MVC guy, but in case of Web API, I would say that you should never use the Initialize() method. In fact, I think this is altogether wrong in terms of object orientation. The name "Initialize" suggests that you want to initialize the object, but there is already a method for that — it's called the constructor...

So you don't want to use Initialize() (or even if you do, my point still stands, so don't stop reading now :) ). What do you do? Well, you might create a factory-like mechanism which can create a version of IMyService based on a version parameter. Something like this:

public static class MyServiceFactory
{
  private static readonly Dictionary<string, IMyService> services= new Dictionary<string, IMyService>
                    { "V1", new MyServiceV1() }
                    { "V2", new MyServiceV2() }
  public static IMyService GetService(string version)
  {
    return services[version];
  }
}

In which case the controller becomes:

[RoutePrefix("api/Values")]
public class ValuesController : ApiController
{
  public ValuesController()
  {
    
  }

  [HttpGet]
  [Route()]
  public IHttpActionResult Get()
  {
    return Ok(MyServiceFactory[GetVersionFromHeader()].MyBusinessAction());
  }
}

This is kinda good. The conceptual problem is that now every service is basically a singleton. That's bad. And usually this is not what you want. And even if you want this, it's bad. I'm not the guy the declare things like "Singletons are always bad", but it's a very strong 99%. And even in the remaining 1%, you shouldn't use them. If you absolutely must, then still don't use them.
But OK, you might figure out a way to make this less static. But what if you have a complex graph of dependencies? You have to create factories and manage the instantiation strategies for every service. That's a complexity that's usually above your paygrade.

Dependency injection containers to the rescue

So you have to find a way to refactor the factory-like mechanisms out of your code and somehow manage instantiation strategy and object lifecycle in complex dependency graphs. Here's the good news: dependency injection containers do just that.
I love the idea of dependency injection. You'll probably see a lot more posts on this blog about them. But let's be honest, containers are the real deal. Without them DI would be a messy, convoluted way of writing software. Containers take away the mess and leave us with the benefits of DI (maintainability, testability, blahblahblah).
My choice of DI container is Autofac. The why is not important now, and even if you don't like it, you might be able to use the idea in your favorite DI container.

Keyed services in Autofac

Autofac has something called keyed services. Normally, you would register a services like this:

var builder = new ContainerBuilder();
builder.RegisterType<MyServiceV1>().As<IMyService>();
var container = builder.Build();

And then you can go ahead and resolve this service:

container.Resolve<IMyService>();

This gives you a MyServiceV1 instance. But you can also use keyed services. With this, you can basically register more than one type to an interface and associate a key with each registration. The key can be anything, like an enum or a string.

var builder = new ContainerBuilder();
builder.RegisterType<MyServiceV1>().Keyed("V1",typeof(IMyService));
builder.RegisterType<MyServiceV2>().Keyed("V2",typeof(IMyService));
var container = builder.Build();

And then, you can resolve the concrete services using the keys:

var v1=container.ResolveKeyed<IMyService>("V1");
var v1=container.ResolveKeyed<IMyService>("V2");

This is pretty awesome. You can even specify with some special attributes that if you have a dependency of type IMyService, then which keyed version should be passed in:

[RoutePrefix("api/Values")]
public class ValuesController : ApiController
{
  private readonly IMyService myService;

  public ValuesController([KeyedFilter("V2")] IMyService myService)
  {
    this.myService = myService;
  }
}

Again, this is super awesome. I mean, really. The only problem is that this is a bit more static in terms of dependency connections. You have to specify which dependency you want at design time and not the client at runtime. But still, a nice step forward.

Resolving dependencies manually

So we have keyed services. Nice. We can register multiple service implementations to an interface and add a version metadata (which can be a string, an enum, or even an actual Version instance). So how can I specify at runtime to load the dependencies that correspond to the version requested by the client? Simple: I have to resolve the dependencies myself. Take the version from the header, resolve the version of the dependency that I want and pass in as a constructor argument. And Autofac does allow me to resolve the parameters myself:

var builder = new ContainerBuilder();
builder.RegisterType<MyServiceV1>().As<IMyService>();
builder.RegisterType<MyServiceV2>().As<IMyService>();
bulder.RegisterType<ValuesController>().WithParameters(
 new ResolvedParamter(
   (_,__) => true,
   (pi, ctx) => ctx.ResolveKeyed(GetVersionFromHeader(), pi.ParameterType)
));
var container = builder.Build();

This basically registers ValuesController into the container, so whenever a ValuesController is needed (like anywhere in the Web API pipeline), a new ValuesController is created (who would have thought, right?). And to create a ValuesController, a specific constructor will be use: the one that has one single parameter. And what parameter will be supplied to the constructor? That's what we specify in the ResolvedParameter instance. To create a ResolvedParameter, you have to specify two delegates. Both take two parameters, one of type ParameterInfo and one of type IComponentContext. The ParameterInfo contains information about the parameter about to be resolved, like the type, the name or attributes. The IComponentContext is basically your resolvation context, you can use this inside the delegate as your container (the one which tries to resolve the service, whose parameters you are trying to resolve; dependency injection-ception :) ). The first lambda is responsible for deciding whether the resolution should be done at all, and the second one is responsible for the actual resolution. I decided that resolution should always be done, and the resolution should go as described before: take the version from the header and then resolve the service that is needed (which can be determined from the ParameterInfo object) using the version as a the key.

And that's it. One last thing that is needed: I want to use this with all kinds of services, not just services that have exactly one constructor parameter. So I decided to go with the Autofac default and use the public constructor with the most arguments and resolve each parameter for that. I also added a little heuristic, so first I try to resolve a service versioned and then if I cannot then use the "unversioned strategy".
So all that remains is adding some fluff. You know, the usual, so that this concept can be actually usable :)

The fluff

First, an attribute to indicate the version of services:

[AttributeUsage(AttributeTargets.Class)]
public sealed class ServiceVersionAttribute : Attribute
{
  public string Version { get; set; }
  public Type Type { get; set; }
}

(Nobody forgets to seal their attributes, right? )

Then add a version service — after all, I'm using dependency injection to keep my code testable, so I cannot really rely on HttpContext supplying me the version if I'm not in a project that actually references Web API dlls:

public interface IVersionService
{
  string DefaultVersion { get; }
  string CurrentVersion { get; }
}
public class HttpContextVersionService : IVersionService
{
  public string CurrentVersion
  {
    get
    {
      return HttpContext.Current.Request.Headers
                        .GetValues("Version")?.Single();
     }
  }

  public string DefaultVersion
  {
    get
    {
      return "V2";
    }
  }
}

And finally add some extension methods that use the ideas from above and use the (previously hopefully registered) IVersionService to get the version (both the current and the default):

public static class AutofacVersioningExtensions
{
  public static IRegistrationBuilder<T, ConcreteReflectionActivatorData, SingleRegistrationStyle>
   RegisterVersioned<T>(this ContainerBuilder builder)
  {
    List<Parameter> parameters = CreateParameterList<T>();

    var versionAttribute = typeof(T).GetCustomAttribute<ServiceVersionAttribute>();
    if (versionAttribute != null)
    {
      return builder.RegisterType<T>()
                    .Keyed(versionAttribute.Version, versionAttribute.Type)
                    .WithParameters(parameters.ToArray());
            }
    return builder.RegisterType<T>()
                  .WithParameters(parameters.ToArray());
  }

  public static IRegistrationBuilder<T, ConcreteReflectionActivatorData, SingleRegistrationStyle>
  RegisterVersioned<T, TService>(this ContainerBuilder builder)
  {
    List<Parameter> parameters = CreateParameterList<T>();
    return builder.RegisterType<T>().As<TService>()
                  .WithParameters(parameters.ToArray());
  }
  
  private static List<Parameter> CreateParameterList<T>()
  {
    var ctor = typeof(T).GetConstructors()
                        .Where(c => c.IsPublic)
                        .OrderByDescending(t => t.GetParameters().Count())
                        .First();
    List<Parameter> parameters = new List<Parameter>();
    foreach (var parameter in ctor.GetParameters())
    {
       parameters.Add(
                new ResolvedParameter( 
                      (_, __) => true,
                      (pi, ctx) => ResolveWithVersioning(pi, ctx)
                  ));
     }
     return parameters;
   }

  private static object ResolveWithVersioning(ParameterInfo pi, IComponentContext ctx)
  {
    var versionService = ctx.Resolve<IVersionService>();
    string version = versionService.CurrentVersion ?? versionService.DefaultVersion;

    object result;
    if (ctx.TryResolveKeyed(version, pi.ParameterType, out result))
    {
      return result;
    }
    return ctx.Resolve(pi.ParameterType);
  }                       
}

Now you can create your services with the attribute on them:


[ServiceVersion(Version = "V1", Type = typeof(IMyService))]
public class MyServiceV1 : IMyService
{
  public string MyBusinessAction()
  {
    return "Original value";
  }
}

[ServiceVersion(Version = "V2", Type = typeof(IMyService))]
public class MyServiceV2 : IMyService
{
  public string MyBusinessAction()
  {
    return "Newer value";
  }
}

Then register your services and the version service:

var builder = new ContainerBuilder();
builder.RegisterType<HttpContextVersionService>().As<IVersionService>();
builder.RegisterVersioned<MyServiceV1>();
builder.RegisterVersioned<MyServiceV2>();
builder.RegisterVersioned<ValuesController>();
var container = builder.Build();

And there you have it! You can go ahead and look up the few missing lines to integrate this with Web API on the Autofac website. Or with anything else, for that matter.

As always, code is available on Github.

Now bear in mind that this is a work in progress. There are still some judgement calls to be made (what happens if there is no version service registered?, what to do if the client specified a wrong version? what if there are no public constructors?), but I was kind of excited to document this idea even at this stage. Because even at this stage, I think it is awesome (it's not all me, Autofac does most of the heavy lifting).
How awesome? Well...

Akos Nagy