SimpleInjector and dynamic registration of services

May 12, 2014 at 7:38 AM
Edited May 12, 2014 at 8:48 AM
Hi,

If my understanding is correct, SimpleInjector locks down its container on first resolution, and everytime a resolution happens it takes up a reference it and that operation it at crux of it being lock-free.

This also was a good enforcement of finalizing all the services for simple applications. But its more and more common today to dynamically load, and unload assemblies at run-time, and replacement, and unregistration of services would enhance application design in a lot more efficient ways.

And now, this shouldn't be difficult to achieve by the usage of lock-free thread-safe dictionary implementations like ConcurrentDictionary, instead of locking it down.

Would this, (in the same form, or a evolved one) be a part of SimpleInjector's future? Or is your design philosophy to stick to enforcing the best practices for most applications. In-fact, the enforcement could be done, while allowing an advanced usage with a flag, may be?

Would love to hear what you guys have in mind.

Cheers,
PVL
Coordinator
May 12, 2014 at 8:06 AM
Locking down the container is a deleberate design strategy for Simple Injector and is not something you should expect ever changing.

We (me and the other Simple Injector contributors) work on big applications (not just simple tiny applications) and the locking behavior of Simple Injector has never limited us. We dynamically load our assemblies at runtime and use unregistered type resolution to load our components in a just-in-time fashion. Dynamically 'unloading' assemblies however is something that's currently impossible in .NET, unless you recycle the entire app domain.

If you have any specific examples of something you'd like to achieve, we would be happy to discuss the design with you and explain what would be the right way to achieve this with Simple Injector.

Cheers
May 12, 2014 at 8:47 AM
Edited May 12, 2014 at 8:48 AM
My statement seems to have been wrongly suggestive of SimpleInjector being more useful only in simpler scenarios. I apologize if it did.

And I agree with your points. There are definitely ways to work it through. I was just wondering, wouldn't it be a better idea to implement the unregistered type resolution using a ConcurrentDictionary (or any similar data-structure) instead of the immutable approach. As being immutable has its costs during a new registration.

And replacement, and unregistering a service is just a possibility that can be provided in an elegant way, if a concurrent dictionary like structure is used to store.

My design was an experiment around a host application that loads, and unloads AppDomains, and with AppDomains being able to register their own plugin, which could potentially be an upgrade to an existing host's internal implementation (Which applies only to the current, and future AppDomains). And it is guaranteed that any AppDomain loaded after this "upgrade" will be unloaded before the AppDomains providing the implementation will be unloaded.

Now, I could easily use different container, but I was wondering wouldn't it be a lot more elegant, if the Container can handle such scenarios. I thought a ConcurrentDictionary approach could possibly in my opinion, expand the usage while still being locked down. But also optionally not do so, if required for scenarios such as the above.

I appreciate your thoughts on it.
May 12, 2014 at 8:52 AM
Also, while SimpleInjector does provide a way for unregistered type, is there a way to handle replacement (potential upgrade) for types?
Coordinator
May 12, 2014 at 9:48 AM
Apology accepted :-).

Just replacing the immutable dictionary with a concurrent dictionary doesn't help much, because of the optimizations that Simple Injector does internally. When you request an object graph, only your request for the top most object actually hits the container's internal dictionary. There are no calls back into the container when resolving the root object's dependencies (and their dependencies, and so on). This -not only- allows the container to be higly performant, it also enables certain advanced (but very interesting) scenarios of analyzing and replacing part of the Expression trees before they get compiled. Take Context Based Injection and runtime decorators for instance of advanced features that make use of this.

Simple Injector burns the creation of a type's dependencies into the delegate of the root type as an optimization (this prevents having to callback into the container recursively). A simple example is a HomeController that depends on a singleton FileLogger (through the ILogger interface). Since FileLogger is a singleton, Simple Injector can (and will) optimize the creation of HomeController by adding the ILogger reference as constant (Expression.Constant) into the construction of the HomeController.

But what would happen if you resolve a HomeController (causing Simple Injector to compile the delegate containing the FileLogger) and after that inform the container that it should replace the FileLogger with a ConsoleLogger. Since the FileLogger instance is compiled into the HomeController's delegate, no matter what you do, the HomeController will always be created with that FileLogger. The only way around this is by invalidating the complete delegate cache every time you change an instance, but that can of course all other sorts of problems; most noticably performance of course, since delegate compilation is far from cheap. Another option is to remove all the optimizations that Simple Injector does, but as I see it, there is no reason for this.

And as I see it, handling replacements of services is not the container's job. If you need this, it is your responsiblitity as a developer to make sure you have the proper mechanism in place. And this is usually a no-brainer. If we take the ILogger abstraction as example, you can create a simple proxy class that allows you to swap any time you need. For instance:
private sealed class LoggerSwapperProxy : ILogger
{
    private readonly Container container;

    public LoggerSwapperProxy(Type loggerType, Container container) {
        this.LoggerType = loggerType;
        this.container = container;
    }

    public Type LoggerType { get; set; }

    public void Log(LogEntry entry) {
        var logger = (ILogger)container.GetInstance(this.LoggerType);
        logger.Log(entry);
    }
}
You can register this proxy as follows:
var loggerProxy = new LoggerSwapperProxy(typeof(FileLogger), container);

container.RegisterSingle<ILogger>(loggerProxy);
And now you can swap implementations any time you like:
loggerProxy.LoggerType = typeof(ConsoleLogger);
If you need to be able to change the logger implementation from within the application, you can add a second abstraction (something like ILoggerImplementationChanger) with a ChangeLogger(Type loggerType) method and let the LoggerSwapperProxy implement that. This way your application code can swap loggers during runtime, without the need to reference the container. Of course the proxy references the container, but this is okay, as long as you define this implementation inside your Composition Root; it will merely be a small peace of infrastructure code.

And this works beautifully, since you already have to proper abstractions (ILogger in this case) in place. The application doesn't know anything about this. I've not yet experienced a scenario where this approach doesn't work.

You might see this as a work around, but IMO this is not a work around, but simply clean and clear design.

Your requirements will be more complex though, I have no doubt about it. If you have any specific examples of what you are trying to achieve, please show them. I'm pretty sure, you can achieve this in a pleasurable and clean way.
Marked as answer by prasannavl on 5/12/2014 at 4:01 AM
May 12, 2014 at 10:26 AM
Edited May 12, 2014 at 10:29 AM
Firstly, you just converted me from being a SimpleInjector experimenter, to a self-proclaimed advocate of it. :)
Apart from being a great piece of software, I'd advocate it just for the support from the community (in this case, yourself).

Now, to the issue at hand, I was just hoping to stay clear of numerous proxies. But I'm beginning to see this as a better and more transparent design. But there's just this one thing itching in my head.

In the above example, inside the proxy LoggerSwapperProxy, the LoggerType is resolved from the container, I see two approaches off the top of my head there. One is everytime I swap, I manually register the LoggerType into the container if it isn't registered. (Which doesn't seem like the best solution in my case, since I can't remove it off the list when you swap again). So the next approach is to load it through unresolved handler, which now opens up container to create any type that is unresolved. So, here, the no-brainer solution would be a controlled instantiation in the unresolved handler.

Is there a better approach to this, or is this what you'd recommend?

And thanks a ton for the explanation in excellent detail.
Coordinator
May 12, 2014 at 10:47 AM
Great! We've got a new fan :-)

If you can, I think you should always try to register all types up front, since this has some very interesting benefits, such as the ability to verify and diagnose the container's configuration.

Without upfront registration the container will not know about that type and will not be able to do analysis on it. But if you need dynamic loading of plugins (for instance the use can drag a DLL to a plugin directory and the application should be able to pick it up without a restart), you can't do upfront registration. But in case of a concrete type, you don't have to do registration at all. By default, Simple Injector does unregistered type resolution for concrete types for you. So my previous example with the LoggerSwapperProxy will just work, as long as the types you supply are concrete (non-generic or closed-generic) types. You might want to add some checks to the ChangeLogger method to ensure that the type actually is an ILogger and is concrete. You might even try to resolve the type before replacing the existing type to prevent your whole application from breaking.
May 12, 2014 at 11:01 AM
I missed out on the fact that concrete types do not require registration at all. Awesome!
You've been of immense help! Thank you.

Cheers,
PVL