2

There is this article that says:

A Composition Root is a (preferably) unique location in an application where modules are composed together.

Only applications should have Composition Roots. Libraries and frameworks shouldn't.

A DI Container should only be referenced from the Composition Root. All other modules should have no reference to the container.

My question is like this:

We have a Console app and in another project a library that performs some algorithm. It has a class AlgorithmFactory, which takes an AlgorithmInput as a parameters and creates an AlgorithmRunner. Facts are:

  1. From the console I want to call: IoC.AlgorithmFactory.CreateAlgorithmRunner(input).Run()
  2. AlgorithmInput contains run-time data that some library classes need for their configuration (for example: RoundingPrecision indicating the number of decimal places that we should round to, or ConnectionString, or some other types of credentials...)
  3. AlgorithmInput also contains data that indicate which implementation of a particular interface should be used (for example: there is an interface ISorter, and two implementations: MergeSorter, RadixSorter. There are classes which depend on ISorter and don't care which one is used, this information will be part of the input)
  4. AlgorithmRunner internally depends on a non-trivial number of classes. Its complexity suggests it acts as a separate sub-application.

I can't really configure the algorithm outside of the library, because I need run-time data for it. But I'm not supposed to reference the container from the library (or any other component). How is this scenario usually solved? (I believe it must be a well-known case).

1
  • 1
    But you already solved it. You already have AlgorithmFactory. Maybe draw a diagram for us to better understand the problem as I don't see where the problem is.
    – Euphoric
    Dec 16, 2018 at 18:16

3 Answers 3

5

You may create and register a factory in your composition root which itself uses the container to resolve components depending on the runtime input data it gets passed as parameter(s). This factory may then get injected into components of your library (preferably as interface).

This way your library does not statically depend on the DI container but still uses it at runtime.

2

OK so, your problem as I understand it is that AlgorithmRunner has dependencies which cant be determined until after you have obtained some runtime data. But you want to configure them in the same IoC setup as everything else in the application

This can be solved in two ways:

  1. Setup all possible dependencies at startup and select between them at runtime.

    This is the simplest solution, either have AlgorithmRunner contain dictionaries of different types of Sorter and its other dependencies, or have the IoC Container have multiple named AlgorithmRunners each set up for an alternative senario.

    Configure them all at startup and then select the correct one at runtime. If the user selects one that's not configured, return an error. Essentially you call*

eg

IoC.Resolve<AlgorithmRunner>(input.RunnerTypeName).Run();
  1. Set the IoC up to use a factory to construct the AlgorithmRunner, which will only be called when an AlgorithmRunner is requested from the IoC Container and have that factory grab the run time data when it is called.

eg

var inputContext = application.InputContext;

IoC.Register<AlgorithmRunner>(() = > {
   return new AlgorithmRunner(inputContext.currentInput);
});

*Although of course you should never be directly calling your IoC container

The third solution is simply to ignore the problem. You have setup your factory in the composition root can call it to get Runners as required.

This is slightly messy as your IoC container is essentially a factory so now you have two and your AlgorithmRunner is unsure about whether its a single use class that should probably be renamed to Algorithm.Run() or a multi use class that you should keep around to run all sorts of varied algorithms

2
  • Sorry for not being clear, but I don't have the AlgorithmFactory yet. It should be creating instances of the AlgorithmRunner, which requires creating instances of many classes, so I wanted the container to do it for me.
    – Patrik Bak
    Dec 16, 2018 at 12:43
  • edited to reflect new info
    – Ewan
    Dec 16, 2018 at 13:24
0

IoC Container an anti-pattern

Using a generic IoC container is in many ways an anti-pattern. It is essentially a controllable singleton, and is more dangerous as most will happily construct any type posing a security concern.

For the most part the same effect can be achieved with lower coupling, and higher cohesion through straight Dependency Injection. Essentially rely on the caller to pass in dependencies to a constructor, or a factory function which constructs the correct sub-type from the given context.

In most cases the caller will know the appropriate dependencies, or will have been passed them. Referring to a global variable in this sense hides relevant configuration information which makes debugging harder.

IoC Container a pattern

There are a few cases where an IoC container makes sense. The general rule of thumb is: the configuration requirements are unknowable when the application was compiled, or unknowable by the user.

  • "Singleton" configuration and access. This implies that these singletons can be loaded from libraries shipped after the main executable was shipped. Still a code smell, at the very least it provides visibility and test-ability.
  • Cross Cutting concerns. There are times where the caller cannot (or should not) provide details about encapsulated concerns. i.e. a Web Service should not allow the user to pass the connection string or adaptor for the database.
  • External Configuration. Essentially the program acts like a library, and defining and configuring its specific activities via a configuration file. The configuration file may be selected as part of invoking the program program config/file/here.conf, or co-located with the binary. This is best used for swiss army knife style applications used in larger workflows.
  • Sand boxing. Essentially the IoC acts as part of the security of a sandbox limiting untrusted code from instantiating just any type, or using just any configuration. This only make sense for generic sandboxes where the capabilities are unknown in advance, such as when loading late-bound trusted libraries containing novel services for use by the sandbox. Otherwise a specialised factory would provide better security guarantees.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.