3

For a long time now we've been fortunate enough to have the Common Service Locator (CSL) to resolve services from an unknown source. However, there has never been any container-agnostic solution for registering these services in the first place. We've always been faced with the problem of having to couple our composition code to a particular IoC container, and furthermore, be committed to using that container throughout our entire application.

I feel I may be trying to achieve the impossible here, but does anyone have any ideas on how to achieve a Common Service Registry (CSR)?

My original idea was to use MEF to resolve various IContainerIntegrators (one class for each container technology), which in turn use MEF to resolve various IXXXContainerBindings (one interface for each technology). Users could then develop applications around container bindings, and just drop their bindings into the BIN directory to achieve a plugin architecture. If they wanted to use a new container technology, then they would just have to develop a new IContainerIntegrator class and accompanying IXXXContainerBinding, which they would then use to write their own bindings. At application startup the CSR aggregates all of the container instances into a single CSL using a ServiceLocatorAggregator class.

I have got this working, but am faced with the following problems:

  • Any binding which makes calls to the current (incomplete) container is unstable, since the previous registrations may be overridden by subsequent bindings. This includes bindings which need to resolve objects to make registration decisions (i.e. 'bind this if that is present').
  • Two bindings may exist which expose the same set of services. However, we want to 'interlace' these registrations. E.g. 'I want services A and C from binding X, and service B and D from binding Y'.
  • Containers resolve services from themselves when auto-wiring dependencies of requested services. E.g. 'container.Bind<This>.To<That>()' will automatically inject the implementation with services resolved from 'container' - not from the aggregated service locator.

Please shout at me if I've completely missed the point here, but isn't this the most decoupled solution for dependency management? Wouldn't it be nice? I'm about to embark on a big enterprise project with a plugin architecture. I don't want to commit to a particular IoC container.

(p.s. This question is about container-agnostic composition which supports discovery. Please do not enter a debate on SL vs DI. SL is used in composition, which is why I've referenced it so much here).

Lawrence Wagerfield
  • 6,471
  • 5
  • 42
  • 84
  • 3
    I think it makes sense not to be dependent on one specific IoC container. But using several of them at the same time? That sounds like a bad idea to me. – svick Jun 04 '11 at 18:00
  • What other options do we have? I don't think we can have both. The Application Vendor may use Autofac, but Plugin Vender A may use Ninject, and Plugin Vendor B may use Castle. – Lawrence Wagerfield Jun 04 '11 at 18:01
  • 1
    I think that what the plugin uses internally shouldn't matter, as long as your plugin API provides all that is necessary directly. – svick Jun 04 '11 at 18:07
  • 1
    SO answer [Dependency Inject (DI) “friendly” library](http://stackoverflow.com/questions/2045904/dependency-inject-di-friendly-library) may be useful to you. – bentayloruk Jun 04 '11 at 18:13
  • @svick - Not sure if I understand your point. Bindings (or plugins) may swap-out existing implementations with new ones. E.g. Our application may depend on a ISearchProvider. The default binding may register with LuceneProvider, but later down the line a new binding is released which registers GoogleProvider. What I'm saying is that these two bindings shouldn't have to use the same container technology... but currently they do. – Lawrence Wagerfield Jun 04 '11 at 18:15
  • @bentayloruk - Have read this before, although all he really states is the basics of DI and how to implement a lightweight container (or 'Facade'). The real problem I'm trying to solve here is how to create a Composition Root which discovers and loads modules of registration code whilst remaining container-agnostic. We're talking about the design of a composition root, not the actual library. – Lawrence Wagerfield Jun 04 '11 at 18:27
  • @Lawrence, it seems to me what you are trying to achieve is similar to: “I don't want to be dependent on one database, what if I use, MS SQL, one plugin uses MySQL and another PostgreSQL. How do I make work together?” If something like that was possible, I don't think it would be worth the effort. – svick Jun 04 '11 at 18:40
  • @lawrence-wagerfield OK. I would put down the IDE and just use [Autofac](http://code.google.com/p/autofac/). I can't see anything but pain at the end of your current road :) – bentayloruk Jun 04 '11 at 18:45
  • @bentayloruk + @svick: Was coming to the same conclusion, may just have to settle-down with a single container! I still feel wrong by forcing plugin vendors to commit to *my* container of choice. – Lawrence Wagerfield Jun 04 '11 at 19:01
  • @lawrence-wagerfield why would the plugin vendor need to know? You can just auto-wire up the components they attribute/mark. Or you can load their plugin and call them with a container wrapper interface that they can use to make registrations (if that's what you want). You can use a single container but they shouldn't have to know about it. – bentayloruk Jun 04 '11 at 19:04
  • I don't like your former approach because I believe implementation code shouldn't be polluted with attributes used for composition. Furthermore you cannot define additional complexities like lifetime. This is what I dislike about MEF. The latter approach also has its downsides. I've seen people implement container wrappers, but then you're just enforcing *your* container upon them (albeit a wrapper), which means they still can't use their own technology which they feel comfortable with. – Lawrence Wagerfield Jun 04 '11 at 19:09
  • @lawrence-wagerfield While the latter has downsides, it is better than making them reference a specific container. You will also be able to limit the API surface to methods that make sense in your domain (and you have the option to express domain concepts in the interface). – bentayloruk Jun 04 '11 at 19:36
  • Related: http://stackoverflow.com/questions/5987841/can-windsor-cooperate-with-another-ioc-container – Mark Seemann Jun 04 '11 at 22:58

2 Answers2

9

The most decoupled (and the best) solution you can achieve is to realize that loose coupling is best achieved through principles and patterns instead of particular technologies.

Use Constructor Injection throughout your application. The ensures that none of your application layers need reference any container at all. Then compose the entire application graph in the root of the application.

You don't need to use a DI Container for this, but if you choose to use a DI Container, you must isolate it to the Composition Root. This means that if you later decide to migrate to a different container, you only need to change the Composition Root. However, Poor Man's DI is also an option.

Steven
  • 166,672
  • 24
  • 332
  • 435
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • How do I make the composition root extendible so it can be modified without the application being recompiled? How do I allow users two write their own plugins which are included into the composition root? What if the authors of these plugins decide to use a container to handle complexities like lifetime? I've seen you use this exact answer several times in previous questions, but it doesn't answer my question. I want a *modular and extendible composition root*. – Lawrence Wagerfield Jun 04 '11 at 21:22
  • Use a DI Container that supports XML configuration (most of them do). – Mark Seemann Jun 04 '11 at 21:30
  • This approach would couple the application with a specific DI Container. The original question proposed a container-agnostic solution. – Lawrence Wagerfield Jun 04 '11 at 21:42
  • It would couple the app with a container, but not the plugins. Isn't this what you want? – Mark Seemann Jun 04 '11 at 22:51
  • Take a look at the discussion here: http://stackoverflow.com/questions/5987841/can-windsor-cooperate-with-another-ioc-container/5988728#5988728 – Mark Seemann Jun 04 '11 at 22:58
  • The author in this post describes the same requirement that I have. Not all plugins are simply library classes. Sometimes they are complex, and require composition code of their own so they can specify complexities like lifetime. Simply stating 'plugins should use DI' is not an answer. My plugins *do* use DI, but what I'm now looking at is adding potentially complex composition rules for these plugins without having to recompile my entire app, or be coupled to the app's container. – Lawrence Wagerfield Jun 05 '11 at 08:58
  • 1
    Yes, I understand that. Did you read the entire discussion? It pretty much explains the tradeoffs you'll have to deal with. Either you provide a *discovery mechanism* or you provide options for explicit configuration (e.g. in XML). The discovery mechanism could be based on attributes like MEF, or perhaps on conventions (such as if there's a unambiguous implementer of an interface, this would be automatically used). At application startup time, you could simply scan all assemblies in a directory and register all interface implementations into the application's container. – Mark Seemann Jun 05 '11 at 11:01
1

There are a number of tricks you can use to avoid having to depend on a specific IoC container throughout most of your code, the simplest of which is to use constructor injection. If you're dead-set on using a Service Locator pattern, just create your own Service Locator class, which wraps the actual IoC container kernel you're planning to use.

That said, the point of an IoC container is to achieve "Inversion of Control": i.e. to move the control from the bottom levels to the top. This means that you need to have a point near the "top" (or "root") of your application that is actually aware of all the service implementations it's going to use, as well as your specific IoC implementation. This should be restricted to a handful of classes. Generally the "Context Root" of an application is the place where you will initialize your IoC container and Service Locator. There should be a specific module or group of modules that takes care of setting up all your bindings.

If you want to allow for plugins, you need to create a specific API for them to use and conform to. Simply allowing other packages to define new IoC bindings willy-nilly is a recipe for disaster, since you don't know how well these different packages will play together.

ASP.NET MVC 3 is a good example of this. They have specific service factory locators that you override within the Global Application_Start method. In order to implement one of these factories, you have to abide by the API that they provide you. But you can create an implementation that uses any IoC container you want, or none at all. You're not changing "bindings" at all. You're just telling the framework that for the current application, you want it to use "this factory" to create controllers or model metadata providers instead of using the default factory.

To use another example that's more applicable to your specific example, let's take the case of an ISearchProvider. You might have a built-in LuceneProvider, and maybe one of your plugins can provide a GoogleProvider. Which of these providers do you want to use? Does the mere presence of the GoogleProviderPlugin mean that the LuceneProvider is no longer available? Should searches somehow combine the results of both of these providers? Should the user be able to choose one or more providers from within the user interface?

Regardless of the answer to these questions, the ultimate point is that you want your application to control this, not the plugin. Rather than giving the plugin carte blanche to muck with your DI bindings, you want to tell the plugin, "I am allowing you to define additional search providers, and here is how you can register them." They can be registered by a variety of means, including class annotations/attributes or the mere presence of a class that implements a given interface. But the important point is that there is an API that specifically defines what they can "plug in" to, and what you require of anyone who builds a plugin.

Now, if the GoogleProvider has dependencies that are defined within the plugin, that plugin can resolve those dependencies however it wants. Hopefully it will use some kind of IoC container, but if it doesn't, that's no skin off your back. You can still be agnostic as to the kind of container they use, if any.

If there are certain services that you expect a SearchProvider to require, you can either include those services, or factories for those services, as part of the initialization API for your plugin. That way your plugin can access those services without needing to be aware of your application's IoC container.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • Again, not sure if my question is being understood. I am fully aware of constructor DI and use it everywhere. However, with this pattern comes the composition root - i.e. where classes are instantiated. The problem I am trying to solve is how to create a flexible composition root which is easy to extend without recompiling the application. The most obvious solution is to use MEF to discover container bindings. However, I want a container-agnostic solution. – Lawrence Wagerfield Jun 04 '11 at 18:33
  • For the record, I'm not dead-set on using SL; its unavoidable. Containers use SL internally. How do you think those dependencies are injected? The container *locates* them internally. Service Location is used to achieve Dependency Injection; they are complementing patterns. DI is not a pattern which can be used in complete isolation. But yes, I agree, some people do use SL inappropriately, but that's not the case here. – Lawrence Wagerfield Jun 04 '11 at 18:37
  • @Lawrence: Does my updated last paragraph make my point about plugins more clear? MVC doesn't require the user to be tied to any particular IoC container, but they still make it easy for the user to decide which implementations are used for the services that they want the user to be able to define. – StriplingWarrior Jun 04 '11 at 18:39
  • @StriplingWarrior: In essence, MVC is exposing its own container for you to populate with factories. That is one approach, but requires the application's code to be modified each time a new implementation is required. This is not a plugin architecture. – Lawrence Wagerfield Jun 04 '11 at 18:46
  • @Lawrence: "Containers use SL internally"... sort of, but not entirely. When people talk about Service Locators, they're generally talking about a static property or singleton somewhere that you can request service implementations from. Ninject uses "kernels," which encapsulate their bindings within a single instance. I can't say "Ninject, give me an X." I have to say, "_kernel, give me X." If you make your main Ninject kernel available via static property, and have various classes ask it for services, that's the "Service Location" that people disapprove of. – StriplingWarrior Jun 04 '11 at 18:50
  • @StriplingWarrior: Slightly offtopic, but I have to disagree. Service Location is the act of locating a service from a provider, be it static or otherwise. 'kernel.Resolve<>' is service location. Ninject will use 'Resolve' internally to inject dependencies, so is using SL. – Lawrence Wagerfield Jun 04 '11 at 18:53
  • Your additional comments regarding my ISearchProvider example provides some valid points. +1 from me. – Lawrence Wagerfield Jun 04 '11 at 22:11