Constructor injection (DI) vs. static factories for cross cutting concerns?
Asked Answered
A

2

8

In most arbitrary applications, there are many cross cutting concerns that need to be addressed among all available layers, e.g. logging, message bus, configuration. What I noticed is that in some classes, they tend to completely blow up the constructor if the modules are being injected using an IoC.

public class MyService : IService 
{
    public MyService(ILogger logger, IAppSettings settings, IEventBus eventBus...)
    {

    }
}

For usual cases of constructor over-injection, I tend to refractor the concerns into building blocks that belong closely together so I get fewer dependencies in a class. However, this is not possible with cross cutting concepts.

Among logging frameworks, static factories / services seem to very popular, e.g.

// Application root
MyLoggerService.SetFactory(log4NetFactory);

// Somewhere
MyLoggerService.GetLogger("name") // returns Log4NetLogger created by Log4NetFactory.

My question is: Is this approach a good one, for all kinds of cross cutting stuff? What are the drawbacks if the code may end up looking like this:

public class MyService : IService
{

    private readonly IReallyNeedThat _dependency;

    public MyService(IReallyNeedThat dependency)
    {
        _dependency = dependency;
    }

    private readonly ILogger _logger = LoggerService.GetLogger("MyService");
    private readonly IEventBus _eventBus = EventBusService.GetEventBus();
    private readonly IConfiguration _configuration = ConfigurationService.GetConfiguration(Level.Roaming)
    private readonly IExceptionHandler _exceptionHandler = ExceptionPolicy.GetHandler();
    private readonly ITracer _tracer = TraceManager.GetDebugTracer();
}
Atrium answered 11/8, 2014 at 11:10 Comment(5)
Not directly related, but interesting nonetheless: #1986217.Suffragette
Read this and thisSemirigid
@MarcelN. Indeed. This is, IMO, the right way of resolving the problem of having too many dependencies/parameters.Atrium
@YuvalItzchakov Great examples, but, as you can see in your second example, the author bundled several domain concerns into a facade that performs a certain domain related task. I don't think this makes sense with cross cuttings concerns.. I mean, in one class I need a logger, a exception handler and a tracer, in the other I need a logger, a tracer and a event bus. How might does facades look like then? ICoreServicesWithLogger, ICoreServicesWithoutEventBus, ICoreServicesForExceptions?Atrium
Make multiple facades, and inject the proper one into the class as needed. What i would do is encapsulate the logger, exception handler, and tracer into one facade service (just because their line of work seems similar), and then inject that facade into another facade with also includes the event bus.Semirigid
S
6

If you are more onto TDD, you can guess easily which approach is better.

With dependency injection, your code becomes more (unit)testable. You can inject dependencies via some mocking framework and create your unit tests without much headache.

But in case of static factories, since your factory classes are (hard)wired into your class, while unit testing there is no way out how you can inject them from outside your class.

Benefits of DI over static factories -

  1. Concurrent Development - Think of a logging service that you are consuming, which is being built by somebody else and you are going to unit test your code (and you don't care unit testing of the logging service since you assume, it should be unit tested when you use it) . You batter use DI, inject the dependency using a mock object and done.

  2. Speed - While unit testing your classes, definitely you wouldn't want them to take long time (so that it gives you a coffee break with every single change to your main class ;) ). You would definitely want you unit test to run in a blink of eye and report any error. Static factory that depends on external resources (e.g. network/DB, FileSystem) is going to take time. You better use DI, use a mock object and done.

  3. Testability - DI helps isolating the client from their dependencies (promotes use of Interfaces), hence improves Testability (via use of mocks).

Supposed answered 11/8, 2014 at 11:33 Comment(1)
+1. I would add that you generally don't want (need) to mock out cross cutting concerns. So in the OP's example, the event bus would be something you probably don't want to be static. I would say this is the case even if you're not doing TDD, but thinking about it in those terms makes it easier to see what could/should be mocked (and thus injected) and what shouldn't.Vamoose
C
8

Moving the dependencies out of the constructor doesn't solve the problem, because you don't lower the amount of dependencies a class has and chances are big that you are still violating Single Responsibility principle and the Open/Close principle, causing your code to be hard to test, hard to change and hard to maintain.

Instead often a good solution is to pull those cross-cutting concerns out of your components and place them into components that are specially tailored for that cross-cutting concern and that component wrap the original component. In other words: create decorators.

This does probably force you to change the design of your classes, because when you don't have generic abstractions to define sets of related services, you will have to define a decorator per abstraction and that would cause a lot of code duplication, which is bad in almost all cases.

So instead, model your system around command/handlers and query/handlers and you'll be in a much better place. You can decorate each piece of business logic with a generic decorator that you define once and reuse all over the place. This keeps your system clean but still very flexible.

Culbreth answered 11/8, 2014 at 15:34 Comment(0)
S
6

If you are more onto TDD, you can guess easily which approach is better.

With dependency injection, your code becomes more (unit)testable. You can inject dependencies via some mocking framework and create your unit tests without much headache.

But in case of static factories, since your factory classes are (hard)wired into your class, while unit testing there is no way out how you can inject them from outside your class.

Benefits of DI over static factories -

  1. Concurrent Development - Think of a logging service that you are consuming, which is being built by somebody else and you are going to unit test your code (and you don't care unit testing of the logging service since you assume, it should be unit tested when you use it) . You batter use DI, inject the dependency using a mock object and done.

  2. Speed - While unit testing your classes, definitely you wouldn't want them to take long time (so that it gives you a coffee break with every single change to your main class ;) ). You would definitely want you unit test to run in a blink of eye and report any error. Static factory that depends on external resources (e.g. network/DB, FileSystem) is going to take time. You better use DI, use a mock object and done.

  3. Testability - DI helps isolating the client from their dependencies (promotes use of Interfaces), hence improves Testability (via use of mocks).

Supposed answered 11/8, 2014 at 11:33 Comment(1)
+1. I would add that you generally don't want (need) to mock out cross cutting concerns. So in the OP's example, the event bus would be something you probably don't want to be static. I would say this is the case even if you're not doing TDD, but thinking about it in those terms makes it easier to see what could/should be mocked (and thus injected) and what shouldn't.Vamoose

© 2022 - 2024 — McMap. All rights reserved.