3

I'm about to get used to Ninject. I understand the principles of Dependency Injection and I know how to use Ninject. But I'm a little confused right now. The opinions drift apart when it comes to the Service Locator pattern.

My application is built upon a strictly modular basis. I try to use constructor injection as much as I can and this works pretty well, though it's a little bit messy (in my opinion).

Now when an extension (external code) wants to benefit from this system, wouldn't it be required to have access to the kernel? I mean right now I have one static class which gives access to all subsystems of my application. An alternative would be having access to the kernel (Service Locator pattern) and grabbing the subsystem dependencies from this one. Here I can easily avoid giving access to the kernel or to be more explicit, not allowing dependencies to the kernel.

But if an extension now wants to use any components (interfaces) of my application, from any subsystem, it would be required to have access to the kernel in order to resolve them because Ninject does not automatically resolve as long as you're not using "kernel.Get()", right?

Peww, it's really difficult explaining this in an understandable way. I hope you guys get what I'm aiming for.

Why is it so "bad" having a dependency to the kernel or a wrapper of it? I mean, you can't avoid all dependencies. For example I still have the one to my "Core" class which gives access to all subsystems. What if an extension wants to register it's own module for further usage?

I can't find any good answer for why this should be a bad approach, but I read it quite often. Moreover it is stated that Ninject does NOT use this approach unlike Unity or similiar frameworks.

Thanks :)

SharpShade
  • 1,761
  • 2
  • 32
  • 45

2 Answers2

4

There are religious wars about this...

The first thing that people say when you mention a Service Locator is: "but what if I want to change my container?". This argument is almost always invalid given that a "proper" Service Locator could be abstract enough to allow you to switch the underlying container.

That said, use of a Service Locator has in my experience, made the code difficult to work with. Your Service Locator must be passed around everywhere, and then your code is tightly coupled to the very existence of a Service Locator.

When you use a Service Locator, you have two main options to maintain modules in a "decoupled" (loosely used here..) way.

Option 1:

Pass your locator into everything that requires it. Essentially this means your code becomes lots and lots of this sort of code:

var locator = _locator;

var customerService = locator.Get<ICustomerService>();
var orders = customerService.GetOrders(locator, customerId); // locator again

// .. further down..
var repo = locator.Get<ICustomerRepository>();
var orderRepo = locator.Get<IOrderRepository>();

// ...etc...

Option 2:

Smash all of your code into a single assembly and provide a public static Service Locator somewhere. This is even worse.. and ends up being the same as above (just with direct calls to the Service Locator).

Ninject is lucky (by lucky I mean - has a great maintainer/extender in Remo) in that it has a heap of extensions that allow you to fully utilise Inversion of Control in almost all parts of your system, eliminating the sort of code I showed above.

Simon Whitehead
  • 63,300
  • 9
  • 114
  • 138
  • Why is it even worse having one static locator which gets accessed from everywhere where it's needed? I mean except the thing with tightly-coupling. And moreover is it even possible to have non-coupled code? I mean, some dependencies will always exist, why then is it so bad if there exist one which resolves all further (and more complex) ones? – SharpShade Sep 04 '14 at 19:01
  • 1
    Your dependencies in a larger application become skewed. Everything needs to reference everything else and after a small amount of development time the only way to satisfy the requirements of the static locator is to put everything in a single assembly. For example, say you have a `ServiceLocator` assembly. Your `Core` assembly needs to reference it to get at the `ServiceLocator` object , but the `ServiceLocator` also needs to reference `Core` to instantiate your dependencies, which is a circular reference. – Simon Whitehead Sep 04 '14 at 22:21
  • 1
    Also, of course there will always be dependencies. The idea with these sorts of patterns is that you invert the dependencies. Instead of relying on concrete objects or a pattern (as is the case when passing around a service locator), you only depend on contracts provided by business modules. – Simon Whitehead Sep 04 '14 at 22:25
  • Hmm, slowly I think this whole DI stuff is the wrong approach for my assembly... I'm not sure if it will help me if I have to pass all references through constructors. And as long as you don't call "kernel.Get()" you don't use the automation of Ninject, so it's redundant using it. If I used a static Service Locator which is placed in the one assembly which is either way referenced by everything, it shouldn't be a problem with ring references. But I can understand that code would be more complex. So I guess the best approach would be not using it and managing this stuff by my own. – SharpShade Sep 05 '14 at 00:54
  • You should still consider it. Once you get it working the first time you will never go back. Even the simplest of applications are nicer to work with when you have a proper DI/IoC setup. – Simon Whitehead Sep 05 '14 at 00:57
  • I'm just not sure if it fits my needs. Something has to control which object should be created depending on the user's choice (AddIn's replacing various aspects of the application) and this seems to not work unless you have a service locator. At least the AddIn-system needs a reference to the kernel in order to replace the bindings. It seems pretty tricky and I'm not yet convinced that this will help me. At least as long as you say that Service Locators are bad (and yes, my application is quite huge and will be even more in the future). It's hard to weigh cost and benefit... – SharpShade Sep 05 '14 at 01:09
2

This is a bit against SO's policy but to extend on Simon`s answer i'll direct you to Mark Seeman's excellent blog post: http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/ Some of the comments to the blog post are very interesting, too.

Now to address your problem with ninject and extensions - which i assume are unknown to you/the composition root at the time when you write them - i would like to point out the NinjectModules. Ninject already features such an extensibility mechanism which is heavily used with/by all ninject.extension.XYZ dlls.

What you'll do is implement some FooExtensionModule : NinjectModule classes in your extensions. The Modules contain the Bind methods. Now you'll tell ninject to load all Modules from some .dll's. That's it.

It's explained in far more greater detail here: https://github.com/ninject/ninject/wiki/Modules-and-the-Kernel

Drawbacks:

  • Extensions depend on Ninject
    • you may need to recompile your extensions when you "update ninject"
    • as the software grows, it will make it more and more expensive to switch DI containers
  • When using Rebind issues may arise which are difficult to track down (well this is the case whether your using Modules or not.)
  • Especially when extension-developers don't know about other extensions, they might create identical or conflicting bindings (such as .Bind<string>().ToConst("Foo") and .Bind<string>().ToConst("Bar")). Again, this is also the case when you're not using Modules, but extensions add one more layer of complexity.

Advantage: - simple and to the point, there's no extra layer of complication/abstraction which you'd need to abstract the container away.

I've used the NinjectModule approach in a not-so-small Application (15k unit/component tests) with a lot of success.

If all you need are simple bindings like .Bind<IFoo>().To<Foo>() without scopes and so on, you might also consider using a simpler system like putting attributes on classes, scanning for these, and creating the bindings in the composition root. This approach is way less powerful but because of that it is much more "portable" (adaptable to be used with other DI container), too.

Dependency Injection an late instantiation

the idea of composition root is, that (whenever possible) you create all objects (the entire object graph) in one go. For example, in the Main method you might have kernel.Get<IMainViewModel>().Show().

However, sometimes this is not feasible or appropriate. In such cases you will need to use factories. There's actually a bunch of answers to this regard on stackoverflow already. There's three basic types:

  • To create an an instance of Foo which requires instances of Dependency1 and Dependency2 (ctor injected), create a class FooFactory which gets one instance of Dependency1 and Dependency2 ctor injected itself. The FooFactory.Create method will then do new Foo(this.dependency1, this.dependency2).
  • use ninject.extensions.Factory:
    • use Func<Foo> as a factory: you can have a Func<Foo> injected and then call it to crate an instance of Foo.
    • use interfaces and .ToFactory() binding (i recommend this approach. Cleaner code, better testability). For example: IFooFactory with method Foo Create(). Bind it like: Bind<IFooFactory>().ToFactory();

Extensions which replace Implementations

IMHO this is not one of the goals of dependency-injection containers. That doesn't mean it's impossible, but it just means you've got to figure it out yourself.

The simplest you could to with ninject would be to use .Rebind<IFoo>().To<SomeExtensionsFoo>(). However, as stated before, that's a bit brittle. If the Bind and Rebind are executed in the wrong sequence, it fails. If there's multiple Rebinds, the last will win - but is it the correct one?

So let's take it one step further. Imagine:

`.Bind<IFoo>().To<SomeExtensionsFoo>().WhenExtensionEnabled<SomeExtension>();`

you can devise your own custom WhenExtensionEnabled<TExtension>() extension method which extends the When(Func<bool> condition) syntax method. You'll have to devise a way to detect whether an extension is enabled or not.

BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68
  • By now I'm not using any complex scopes or something similiar. Just the Singleton scope for my core classes. One of the main reasons for my question is that I want my AddIn system to be as flexible and automatic as possible. Now if an extension overrides any default interface (lets say we have a GUI element which is based on an interface - the extension has an own version of this element) because the user wants to use the extended version instead of the vanilla variant, (how) can I use Ninject to automatically create the correct instance? – SharpShade Sep 04 '14 at 19:07
  • And what I don't get is: I can access the Kernel from my base application which only starts the subsystems but has no logic (the real program is outsourced into an extension!) Now if I want to instantiate a new object of type "IMyInterface" how can I do this with Ninject if I don't have access to the Kernel or don't have a service locator? I can't figure out how Ninject would save some work if I can only instantiate stuff in my base application ... – SharpShade Sep 04 '14 at 19:09
  • i've extended to answer to cover your questions – BatteryBackupUnit Sep 05 '14 at 06:12
  • Great! Thanks, that helps a lot. So I guess this approach is the completely false one for my application. Moreover I'm not sure if DI will help much. My application is strongly modular built, so the only reference is back to the Core assembly which just contains all the interfaces. References to other assemblies are not required in any case. I guess this is already neatly decoupled, but still tightly coupled to the Core, of course. But using Ninject wouldn't make anything easier nor would it help me much. Just for decoupling the Core isn't much of benefit and AddIns are getting more difficult. – SharpShade Sep 05 '14 at 09:18