Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should a dependency be injected many "levels" up than it is needed?

I'm writing a C# ASP.NET MVC web application using SOLID principles.

I've written a ViewModelService, which depends on a AccountService and a RepositoryService, so I've injected those two services in the the ViewModelServer.

The PermissionService depends on the HttpContextBase in order to use GetOwinContext() to get an instance of the UserManager. The controller has an instance of HttpContextBase that needs to be used - so it seems like I have to inject the HttpContextBase instance into the ViewModelService which then injects it into the PermissionService.

So, in terms of code I have:

public ViewModelService

public CategoryRepository(ApplicationDbContext context, IPermissionService permissionservice)

public AccountService(HttpContextBase httpcontext, IPrincipal securityprincipal)

to instantiate the ViewModelService, I then do this:

new ViewModelService(
    new CategoryRepository(
            new ApplicationDbContext(), 
            new PermissionService(
                new AccountService(HttpContext, Thread.CurrentPrincipal),
                new UserPasswordRepository(new ApplicationDbContext()),
                new ApplicationSettingsService())),
    new PasswordRepository(
            new ApplicationDbContext(), 
            new PermissionService(
                new AccountService(HttpContext, Thread.CurrentPrincipal), 
                new UserPasswordRepository(new ApplicationDbContext()),
                new ApplicationSettingsService())),
    new ModelValidatorService());

Should a dependency be injected from that many "levels" up, or is there a better way?

like image 685
binks Avatar asked Dec 15 '14 21:12

binks


2 Answers

There's a balance to be struck.

On the one hand, you have the school of thought which would insist that all dependencies must be exposed by the class to be "properly" injected. (This is the school of thought which considers something like a Service Locator to be an anti-pattern.) There's merit to this, but taken to an extreme you find yourself where you are now. Just the right kind of complexity in some composite models, which themselves have composite models, results in aggregate roots which need tons of dependencies injected solely to satisfy dependencies of deeper models.

Personally I find that this creates coupling in situations like this. Which is what DI is intended to resolve, not to create.

On the other hand, you have the school of thought which allows for a Service Locator approach, where models can internally invoke some common domain service to resolve a dependency for it. There's merit to this, but taken to an extreme you find that your dependencies are less known and there's a potential for runtime errors if any given dependency can't be resolved. (Basically, you can get errors at a higher level because consuming objects never knew that consumed objects needed something which wasn't provided.)

Personally I've used a service locator approach a lot (mostly because it's a very handy pattern for introducing DI to a legacy domain as part of a larger refactoring exercise, which is a lot of what I do professionally) and have never run into such issues.

There's yin and yang either way. And I think each solution space has its own balance. If you're finding that direct injection is making the system difficult to maintain, it may be worth investigating service location. Conversely, it may also be worth investigating if the overall domain model itself is inherently coupled and this DI issue is simply a symptom of that coupling and not the cause of it.

like image 117
David Avatar answered Oct 13 '22 18:10

David


Yes, the entire intent of Dependency Injection is that you compose big object graphs up-front. You compose object graphs from the Composition Root, which is a place in your application that has the Single Responsibility of composing object graphs. That's not any particular Controller, but a separate class that composes Controllers with their dependencies.

The Composition Root must have access to all types it needs to compose, unless you want to get into late-binding strategies (which I'll generally advise against, unless there's a specific need).

like image 24
Mark Seemann Avatar answered Oct 13 '22 17:10

Mark Seemann