2
votes

I've read a book "Dependency injection in .NET" by Mark Seemann and it opened my eyes on many things. But still few question left. Here is one of them:

Let's say we have a WCF service exposing API for working with some database:

public class MyService : IMyService
{
    private ITableARepository _reposA;
    private ITableARepository _reposB;
    //....

    public IEnumerable<EntityA> GetAEntities()
    {
        return _reposA.GetAll().Select(x=>x.ToDTO())
    }

    public IEnumerable<EntityB> GetBEntities()
    {
        return _reposB.GetAll().Select(x=>x.ToDTO())
    }

    //...
}

There may be dozens of repositories service depend on. Some methods use one, some methods another, some methods use few repositories.

And my question is how to correctly organize injection of repository dependencies into service?

Options I see:

  1. Constructor injection. Create a huge constructor with dozens of arguments. Easy for usage, but hard for managing parameters list. Also it's extreemely bad for performance as each unused repository is a waste of resources even if it doesn't use separate DB connection.
  2. Property injection. Optimizes performance, but usage becomes non-obvious. How should creator of the service know which properties to initialize for specific method call? Moreover this creator should be universal for each method call and be located in the composition root. So logic there becomes very complicated and error-prone.
  3. Somewhat non-standard (not described in a book) approach: create a repository factory and depend on it instead of concrete repositories. But the book say factories are very often used incorrectly as a side way to overcome problems that can be resolved much better with proper DI usage. So this approach looks suspicious for me (while achieving both performance and transparency objectives).

Or is there a conceptual problem with this relation 1 to many dependencies?

I assume the answer should differ depending on service instance context mode (probably when it's Single instance, constructor injection is just fine; for PerCall option 3 looks best if to ignore the above warning; for perSession everything depends on the session lifetime: whether it's more close to Single instance or PerCall).

If it really depends on instance context mode, then it becomes hard to change it, because change requires large changes in the code (to move from constructor injection to property injection or to repository factory). But the whole concept of WCF service ensures it is simple to change the instance context mode (and it's not so unlikely that I will need to change it). That makes me even more confused about DI and WCF combination.

Could anyone explain how this case should be resolved correctly?

1
You are wrong in 1. both in terms of performace and the number of arguments blog.ploeh.dk/2010/01/20/… - Wiktor Zychla
@WiktorZychla, good argument. Though if we need PerCall instance context mode, and follow recommendation that SQL connection should be closed as soon as possible, we end up with creating dozens of new repositories every time we need one. And I once saw a database with 200+ tables. Fortunately repositories are not so expensive objects to create. But doesn't it look strange from design perspective? - Sasha
It isn't, I wouldn't care on how many instances are created as long as you properly close all opened connections. - Wiktor Zychla

1 Answers

5
votes

Create a huge constructor with dozens of arguments

You should not create classes with a huge number of constructor arguments. This is the constructor over-injection code-smell. Having constructors with a huge amount of arguments is an indication that such class does too much: violates the Single Responsibility Principle. This leads to code that is hard to maintain and extend.

Also it's extremely bad for performance as each unused repository is a waste of resources

Have you measured this? The amount of constructor arguments should be mainly irreverent for the performance of the application. This should not cause any noticeable difference in performance. And if it does, it becomes be time to look at the amount of work that your constructors do (since injection constructors should be simple) or its time to switch to a faster DI container if your constructors are simple. Creating a bunch of services classes should normally be blazingly fast.

even if it doesn't use separate DB connection.

The constructors should not open connections in the first place. Again: they should be simple.

Property injection. Optimizes performance How should creator of the service know which properties to initialize for specific method call

The caller can't reliably determine which dependencies are required, since only constructor arguments are typically required. Requiring properties results in temporal coupling and you lose compile-time support.

Since the caller can't determine which properties are needed, all properties need to be injected and this makes the performance equivalent as with constructor injection, which -as I said- should not be a problem at all.

Somewhat non-standard (not described in a book) approach: create a repository factory and depend on it instead of concrete repositories.

Instead of injecting a repository factory, you could inject a repository provider, a pattern which is better known as the Unit of Work pattern. The unit of work may give access to repositories.

I assume the answer should differ depending on service instance context mode

No, since you should never use the WCF 'Single' mode. In most cases the dependencies you inject into your WCF services are not thread-safe and should not outlive a single request. Injecting them into a singleton WCF service causes Captive Dependencies and this is bad because it leads to all kinds of concurrency bugs.

The core problem here seems that your WCF Service classes are big and violate the Single Responsibily Principle, causing them to hard to create, maintain, and test. Fix this violation by either:

  1. Splitting them up in multiple smaller classes, or
  2. Moving functionality out of them into aggregate services and apply patterns such as the command/handler and query/handler patterns.