Question

I often face a problem of having a class that needs both a dependency and some string value to be initialized. For instance consider the following code:

public class CustomerService
{
   public CustomerService(ITcpConnection connection, string realm)
   {
   }
}

The problem with it is that it's not easy get that out of the DI container because of the additional string parameter. What are the best ways to fix this issue? These are the way I can think of:

  1. Initialize method (It's not good enough as it provides the temporal coupling by Mark Seeman's definition and I agree that it's not a very good way)
  2. Abstract factory (I don't really need one extra layer of indirection because the parameter is a configuration and I know it at the time I do the configuration)
  3. Use as in the example and register the instance of the class in DI container
Was it helpful?

Solution

Mixing primitive configuration values and (service) dependencies in the same constructor can best be avoided. They complicate the DI configuration, and often lead to an unreadable and fragile Composition Root.

Do the following instead:

Option 1: Abstract that configuration value

When the same configuration value is injected into multiple classes, there's often an abstraction missing. A good example of this is the injection of a string connectionString value into all repositories. In this case the application likely lacks an IConnectionFactory abstraction (or something similar). Instead of injecting the connection string into many classes, the connection string should be injected into the IConnectionFactory implementation (that probably only takes that connection string as its constructor argument) and let other services depend on IConnectionFactory instead of string. This way those services don't have to deal with the creation of connections; the connection factory can do this.

Option 2: Wrap the configuration value into configuration type

Primitive types cause ambiguity when it comes to resolving types. That string you are injected, is that a file path or a connection string? That int you are injecting, is that the number of retries to do, or the minimum age for customers to buy items?

To prevent the ambiguity, it is good to wrap all configuration values of a class into its own configuration type, even if the class requires just one value. Take for instance at this SqlUnitOfWorkSettings that wraps a TimeSpan.

public sealed class SqlUnitOfWorkSettings
{
    public readonly TimeSpan ConnectionTimeout;

    public SqlUnitOfWorkSettings(TimeSpan connectionTimeout)
    {
        this.ConnectionTimeout = connectionTimeout;
    }
}

Instead of depending on TimeSpan, the SqlUnitOfWork can now depend on SqlUnitOfWorkSettings instead:

public sealed class SqlUnitOfWork : IUnitOfWork
{
    private readonly SqlUnitOfWorkSettings settings;

    public SqlUnitOfWork(SqlUnitOfWorkSettings settings)
    {
        this.settings = settings;
    }
}

The use of the new configuration class simplifies registration in your DI Container, because of the lack of ambiguity:

container.RegisterInstance(
    new SqlUnitOfWorkSettings(connectionTimeout: TimeSpan.FromSeconds(30));
container.Register<IUnitOfWork, SqlUnitOfWork>(Lifestyle.Scoped);

Option 3: Create a sub type

In case the use of configuration classes is not an option (for instance, because you can't change the source code) you can derive a sub type that is placed inyour Composition Root and defines the proper constructor and register that sub type instead:

public class MyService : SomeExternalService
{
    public MyService(ISomeDependency dep) : base(dep, "My Config Value") { }
}

// Registration
container.Register<IService, MyService>();
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top