Passing dependencies the "right way"

602 Views Asked by At

When injecting dependencies into a constructor, I'm not quite sure if it breaks SOLID if I do the following:

public MyClass(IConfiguration configuration)
{
    _port = configuration.GetValue("Port");
}

instead of passing the pure value like so:

public MyClass(int port)
{
    _port = port;
}

The first one seems to be some kind of a Service Locator.

Question here is: Is it okay to pass complex dependencies just to retrieve a single value the class actually relies on?

To me it doesn't look that wrong, but it unnecessarily hides the real dependency which could get passed much easier.

1

There are 1 best solutions below

0
Steven On

From perspective of the SOLID principles, I see no clear violation:

  • Single Responsibility: Whether or not you inject port or IConfiguration doesn't change the number of responsibilities that MyClass has.
  • Open/Closed: This principle is about preventing sweeping changes. I don't see how injecting port would prevent sweeping changes compared to injecting IConfiguration.
  • Liskov Substitution: This principle is about letting derivatives behave as their base class specification. Injection of IConfiguration has no impact on LSP.
  • Interface Segregation: This principle pushes towards the use of narrow interfaces. This is a bit more tricky, and a closer inspection of the code might be needed to see whether IConfiguration is wide and violates ISP. From just this code, however, I would say there is no ISP problem.
  • Dependency Inversion: This principle states that we should program to abstractions, and -almost as importantly- it states that the client should own the abstraction. In case IConfiguration is a framework-defined abstraction, we might consider this code to violate the DIP. But it's not always possible or feasible to remove all dependencies to framework abstractions, so we should be pragmatic. Whether or not it is pragmatic to take a dependency on IConfiguration is impossible to say in your case.

There is, however, other guidance besides the SOLID principles that might help us out here:

  • The injection constructor's body should be simple: When it comes to DI, the rule is that an Injection Constructor should do no more than receiving the dependencies. It should not use it. The MyClass constructor, however, is using IConfiguration inside the constructor.
  • The injection constructor's definition should be minimal: An injection constructor should only be provided with just the dependencies it requires to function. It's a sort of an ISP for constructors and relates to Nikola’s 4th law of IoC. In this case you are providing an interface (IConfiguration) with broad capabilities. In your question you are referring to the Service Locator anti-pattern in this regards. Although injecting IConfiguration can certainly not be regarded as a Service Locator, from loading configuration values, IConfiguration as similar capabilities. It's better to narrow this down and be as explicit as possible and int port is much more explicit compared to IConfiguration.

So with all that in place, let me try to answer your question:

Is it okay to pass complex dependencies just to retrieve a single value the class actually relies on?

Considering the guidance on DI above, I would say that it is better to either inject int port, or a typed configuration class that wraps int port, that is specific to MyClass; e.g. MyClassSettings:

public MyClass(MyClassSettings settings)
{
    _settings = settings;
}

Both convey the same amount of information, but the latter is less ambiguous, as int could mean anything, while MyClassSettings is very precise and easily resolved from a DI Container.