How to avoid tight IoC coupling in non-deterministic resolution scenarios?
Normally IoC should never go into the code below the level of module/component registration. That's the rule.
But what if some specific component needs to resolve components based on some conditions (i.e. resolving by name when name is passed externally)?
You can use dictionary if the components are singletons, but this would not work if they have to be resolved in the current scope. For example, we might need to execute some workflow class knowing only its name and interface. And some workflow class (neither we know, nor do we care) could just need to access the data store within the scope of the current session.
These simple interface declarations help me to manage scenarios like this with a couple of lines of code, while keeping the IoC intrusion minimal:
public interface IResolver<Param, Object>
{
Object Resolve(Param param);
}
public interface IResolver<Object> : IResolver<string, Object> { }
Additionally there is a simple class to lend IoC to the components with inlining:
public class Resolver<Param, Object> : IResolver<Param, Object>
{
private readonly Func<Param, Object> _function;
public Resolver(Func<Param, Object> function)
{
_function = function;
}
public Object Resolve(Param param)
{
return _function(param);
}
}
public sealed class Resolver<Object> :
Resolver<string, Object>, IResolver<Object>
{
public Resolver(Func<string, Object> function) : base(function)
{
}
}
With that you can register named resolution of some interface (ICommand, for example) with this nice statement:
builder.Register(scope => new Resolver<ICommand>(
name => scope.ResolveByName<ICommand>(name)))
.As<IResolver<ICommand>>()
.WithScope(InstanceScope.Container);
and then it could be later used in any component like
public sealed class TaskExecutor
{
private readonly Resolver<ICommand> _commands;
public TaskExecutor(Resolver<ICommand> commands)
{
_commands = commands;
}
...
_commands.Resolve(commandName).Execute();
...
}
Remarks:
- The resolution interfaces should always be explicitly written. That's just to keep the code under control.
- This function-based resolver interface simplifies unit testing quite a bit (especially if you use MockContainer).
- Do not try this code at home with your IoC container unless this container is Autofac.
Using some global static class (i.e.: IoC.ResolveByName...) is not a valid solution either, because:
That's global static class and its usage just complicates code management and testing
- Static resolution would not have any clue about the current scope (rendering it useless)
Thursday, February 28, 2008 at 23:22
Reader Comments (5)
[...] it seems that in addition to ruling the IoC coupling out of the component code (while keeping its resolution powers in) we can simply rule out the entire ORM out of the code as [...]
[...] How to avoid tight IoC coupling in non-deterministic resolution scenarios? [...]
[...] is a bit similar to the Resolver pattern of abstracting away from IoC, that I’ve talked about before. But there is an important modification in this scenario: resolution calls upon the [...]
First of all, great blog! Lots of great information.
Now, on to my point. I don't really understand this. You've abstracted your IOC container, but you're still bound to its resolution principle. I don't really know how you're benefiting by this. Instead of sprinkling around your IOC container explicitly, you're doing it with the Resolver. I think the bottom line is that there shouldn't be any component-level code resolving dependencies. I don't think there are any exceptions to that. If you have to rely on IOC at your component layers, you probably need to start refactoring.
Thanks :)
@Serge,
Think around the lines of Lazy{T} and similar constructs being introduced by MEF (and in Autofac 2.x). Sometimes we need to manage non-deterministic component creation, resolution or lifetime within another controller. At the same time we need to keep all components testable, reusable and detached from IoC.
This makes Resolver/Lazy implementations a worthy choice in some scenarios.