Ways of keeping configuration code out of logic code using Dependency Injection
Asked Answered
S

4

36

How can keep all the configuration file code out of my logic code using Settings (ApplicationSettingsBase) and Dependency Injection?

With configuration I mean a customer specific configuration file.

Do I really have to inject a configuration class everytime I need it or is there another pattern?

It would be great to get some sample code!

Samples:

Static Configuration:

public static class StaticConfiguration
{
    public static bool ShouldApplySpecialLogic { get; set; }
    public static string SupportedFileMask { get; set; }
}

public class ConsumerOfStaticConfiguration
{
    public void Process()
    {
        if (StaticConfiguration.ShouldApplySpecialLogic)
        {
            var strings = StaticConfiguration.SupportedFileMask.Split(',');
            foreach (var @string in strings)
            {

            }
        }
    }
}

Non static Configuration:

public interface IConfiguration
{
    bool ShouldApplySpecialLogic { get; set; }
    string SupportedFileMask { get; set; }
}

public class Configuration : IConfiguration
{
    public bool ShouldApplySpecialLogic { get; set; }
    public string SupportedFileMask { get; set; }
}

public class Consumer
{
    private readonly IConfiguration _configuration;

    public Consumer(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public void Process()
    {
        if (_configuration.ShouldApplySpecialLogic)
        {
            var strings = _configuration.SupportedFileMask.Split(',');
            foreach (var @string in strings)
            {

            }
        }
    }
}

Static Context with non static configuration:

public static class Context
{
    public static IConfiguration Configuration { get; set; }
}

public class ConsumerOfStaticContext
{
    public void Process()
    {
        if (Context.Configuration.ShouldApplySpecialLogic)
        {
            var strings = Context.Configuration.SupportedFileMask.Split(',');
            foreach (var @string in strings)
            {

            }
        }
    }
}
Stereoscope answered 2/9, 2011 at 20:25 Comment(3)
what you want is an Inversion of Control ContainerMylohyoid
@Nico what I want to get is an explanation of seperating logic code from configuration by using an inversion of control container.Stereoscope
I wrote a blog post explaining how and why we use StructureMap to keep our configuration separate from our logic: lostechies.com/joshuaflanagan/2009/07/13/… The functionality described in that post is now available in the FubuCore utility library (you can get it via nuget): github.com/DarthFubuMVC/fubucore/tree/master/src/FubuCore/…Thankless
B
27

The important part to realize is that configuration is only one among several sources of values that drive your application's behavior.

The second option (non-static configuration) is best because it enables you to completely decouple the consumer from the source of the configuration values. However, the interface isn't required, as configuration settings are normally best modeled as Value Objects.

If you still want to read the values from a configuration file, you can do that from the application's Composition Root. With StructureMap, it might looks something like this:

var config = (MyConfigurationSection)ConfigurationManager.GetSection("myConfig");

container.Configure(r => r
    .For<Consumer>()
    .Ctor<MyConfigurationSection>()
    .Is(config));
Benjamin answered 4/9, 2011 at 15:48 Comment(3)
Seeman, using your approach would be cumbersome, because I have more than 100 different consumers. So for every consumer I have to configure my IoC registration, haven't I?Stereoscope
Use a convention. With StructureMap, the entry point is the Scan method.Benjamin
thank you :) I will take a look and think about your approach.Stereoscope
D
29

Configuration classes reduce cohension and increase coupling in the consumers. This is because there may be many settings that don't relate to the one or two needed by your class, yet in order to fulfill the dependency, your implementation of IConfiguration must supply values for all of the accessors, even the irrelevant ones.

It also couples your class to infrastructure knowledge: details like "these values are configured together" bleed out of the application configuration and into your classes, increasing the surface area affected by changes to unrelated systems.

The least complex, most flexible way to share configuration values is to use constructor injection of the values themselves, externalizing infrastructure concerns. However, in a comment on another answer, you indicate that you are scared of having a lot of constructor parameters, which is a valid concern.

The key point to recognize is that there is no difference between primitive and complex dependencies. Whether you depend on an integer or an interface, they are both things you don't know and must be told. From this perspective, IConfiguration makes as much sense as IDependencies. Large constructors indicate a class has too much responsibility regardless of whether the parameters are primitive or complex.

Consider treating int, string and bool like you would any other dependency. It will make your classes cleaner, more focused, more resistant to change, and easier to unit test.

Drivein answered 4/9, 2011 at 18:42 Comment(11)
I think this is the best answer because it recognises that classes do not need to know about IConfiguration but just what their actual dependencies are. You may have cause one day to have two instances of a class configured different way. e.g. "How do the results differ if we use the co-confabulator algorithm prefered by some installations instead of the reverse-hindlebrook preferred by others?"Reinhold
Did I get you right, that you would not use a complex type, but simple types like int, string and bool? If so what if you have 5 different configuration parameters and maybe 2 dependencies. This would bloat your constructor. Maybe one could use a specific complex type for each case where you need some configuration. Something like FileParserConfiguration, BootConfiguration, DatabaseConfiguration. I am still not sure what I should use :)Stereoscope
@Rookian: I would look at that situation as having 7 dependencies. As with any class that has a large number of dependencies, there is some refactoring that needs to happen to reduce the class's responsibility. Constructor bloat is a symptom of a class that does too much; it doesn't really have anything to do with primitive values.Drivein
@Rookian: Put another way: grouping constructor values under an IConfiguration type may reduce the number of constructor parameters but it doesn't reduce complexity, which is the true danger behind constructor bloat.Drivein
I agree with you having a lot of many dependencies is a signal of violating SRP. But it is also a bad practice to use simple types instead of a complex type. Simple types are bad in changeability. And I think having complex cohesive types can make things easier to understand. So grouping things into classes makes things more understandable.Stereoscope
@Rookian: Do you have a reference I can look at that goes into more detail about primitive types being a bad practice? I don't follow the line of reasoning. Under all the layers of abstraction of, say, a data access framework, some class somewhere needs to know a connection string. Primitive types should be well-hidden, but you need them eventually. It is intuitive to me that if class Foo only needs value A, then bundling it with unused, unrelated values B, C, and D increases complexity and coupling with no benefit to Foo. It just leaks infrastructure knowledge into Foo.Drivein
Well in my case the values are related, because all of them belong to the category of configuration. Not all properties has to be used within a method. Have a look at: #7325283 See also the book from Robert C. Martin Clean Code, Argument Objects page 43.Stereoscope
@Rookian: I agree that argument objects are a valid and useful approach to grouping values. The key factor is why they are grouped: configuration is a technical organization of concepts, while argument objects tend to be oriented around the problem domain. If two values are grouped solely because they are configurable, that doesn't effectively communicate their purpose. If you want to group a host name and port, for example, IServerContext is far more expressive than IConfiguration. The grouping should favor intent over mechanism.Drivein
yes I think we have have the same opinion :) My example is just bad. But one question. Would you use a configuration context solely or would you choose composition using a configuration root? So DbConfiguration / PathConfiguration or Configuration.DbConfiguration / Configuration.PathConfiguration?Stereoscope
@Rookian: I generally defer to composition, since it requires making less decisions at the point of usage and thus results in reduced coupling.Drivein
Another name for the idea of "Don't use primitive types" is the Primitive Obsession code smell. In C++ this would be as easy as a #typedef but us C# folks don't get that option. I've created a NuGet package called LikeType which is similar to the typedef in C++ and makes it easy to avoid Primitive Obsession.Foudroyant
B
27

The important part to realize is that configuration is only one among several sources of values that drive your application's behavior.

The second option (non-static configuration) is best because it enables you to completely decouple the consumer from the source of the configuration values. However, the interface isn't required, as configuration settings are normally best modeled as Value Objects.

If you still want to read the values from a configuration file, you can do that from the application's Composition Root. With StructureMap, it might looks something like this:

var config = (MyConfigurationSection)ConfigurationManager.GetSection("myConfig");

container.Configure(r => r
    .For<Consumer>()
    .Ctor<MyConfigurationSection>()
    .Is(config));
Benjamin answered 4/9, 2011 at 15:48 Comment(3)
Seeman, using your approach would be cumbersome, because I have more than 100 different consumers. So for every consumer I have to configure my IoC registration, haven't I?Stereoscope
Use a convention. With StructureMap, the entry point is the Scan method.Benjamin
thank you :) I will take a look and think about your approach.Stereoscope
F
2

One way is to inject a configuration interface like you post. Here are a couple other ways.

Exposing a Setter

class Consumer
{
    public bool ShouldApplySpecialLogic { get; set; }

    ...
}

In the composition root, you can read a config file or hardcode it. Autofac example:

builder.RegisterType<Consumer>().AsSelf()
    .OnActivated(e => e.Instance.ShouldApplySpecialLogic = true);

This is probably only advisable when you have a good default

Constructor Injection

public class Server
{
    public Server(int portToListenOn) { ... }
}

In the composition root:

builder.Register(c => new Server(12345)).AsSelf();
Farmann answered 2/9, 2011 at 22:8 Comment(1)
I am scared about having a lot of constructor parameters when using constructor injection. Setter injection would avoid this, but then there is an additional new approach of getting dependencies and I think this would make it more complicated (mixing constructor and setter injection.Stereoscope
G
0

In my applications I do what you have done above with IoC. That is to say, having my IoC container (StructureMap also) inject an IApplicationSettings into my classes.

For example, in an ASP.NET MVC3 project it may look like:

Public Class MyController
    Inherits Controller

    ...
    Private ReadOnly mApplicationSettings As IApplicationSettings

    Public Sub New(..., applicationSettings As IApplicationSettings)
        ...
        Me.mApplicationSettings = applicationSettings
    End Sub

    Public Function SomeAction(custId As Guid) As ActionResult
         ...

         ' Look up setting for custId
         ' If not found fall back on default like
         viewModel.SomeProperty = Me.mApplicationSettings.SomeDefaultValue

         Return View("...", viewModel)
    End Function
End Class

My implementation of IApplicationSettings pulls most things from the app's .config file and has a few hard-coded values in there as well.

My example wasn't logic flow-control (like your example), but it would have worked just the same if it was.

The other way to do this would be to do a service-locator type pattern, where you ask your Dependency Injection container to get you an instance of the configuration class on-the-fly. Service-Location is considered an anti-pattern generally, but might still be of use to you.

Gracie answered 2/9, 2011 at 21:9 Comment(2)
What do you think about "Static Context with non static configuration"?Stereoscope
@Stereoscope Seems like a testable solution as well, and completely avoids DI. It might not be as "flexible" as needed. With DI, your IoC Container can provide different implementations for different classes (if needed); where as using the static route you are pretty much locked into one implementation of the configuration. Might not be a problem in your situation, just thought I would mention it.Gracie

© 2022 - 2024 — McMap. All rights reserved.