4

I am looking for best practices of avoiding constructor injection overuse. For example I have Meeting entity which has few sub entities like shown below:

  • Meeting
    1. MeetingContacts
    2. MeetingAttendees
    3. MeetingType
    4. Address
    5. MeetingCompanies
    6. MeetingNotes

MeetingService class looks like below:

public class MeetingService
{
    private readonly IMeetingContactRepository _meetingContactRepository;
    private readonly IMeetingAttendeeRepository _meetingAttendeeRepository;
    private readonly IMeetingTypeRepository _meetingTypeRepository;
    private readonly IAddressRepository _addressRepository;
    private readonly IMeetingCompanyRepository _meetingCompanyRepository;
    private readonly IMeetingNoteRepository _meetingNoteRepository;
    private readonly IMeetingRepositoy _meetingReposity;

    public MeetingService(IMeetingRepositoy meetingReposity, IMeetingContactRepository meetingContactRepository, IMeetingAttendeeRepository meetingAttendeeRepository, 
        IMeetingTypeRepository meetingTypeRepository, IAddressRepository addressRepository, 
        IMeetingCompanyRepository meetingCompanyRepository, IMeetingNoteRepository meetingNoteRepository)
    {
        _meetingReposity = meetingReposity;
        _meetingContactRepository = meetingContactRepository;
        _meetingAttendeeRepository = meetingAttendeeRepository;
        _meetingTypeRepository = meetingTypeRepository;
        _addressRepository = addressRepository;
        _meetingCompanyRepository = meetingCompanyRepository;
        _meetingNoteRepository = meetingNoteRepository;
    }

    public void SaveMeeting(Meeting meeting)
    {
        meetingReposity.Save();
        if(Condition1())
            _meetingContactRepository.Save();
        if(Condition2())
            _meetingAttendeeRepository.Save();
        if(Condition3())
            _meetingTypeRepository.Save();
        if(Condition4())
            _addressRepository.Save();
        if(Condition5())
            _meetingCompanyRepository.Save();
        if(Condition6())
            _meetingNoteRepository.Save();
    }
    //... other methods
}

Here are just seven dependencies but real code contains much more of them. I used different techniques described in the "Dependency Injection Constructor Madness" but I have not found how to deal with repository dependencies.

Is there any way how I can reduce the number of dependencies and keep the code testable?

Community
  • 1
  • 1
k0stya
  • 4,091
  • 29
  • 39
  • 2
    Create a MeetingConfiguration class where the constructor 'herds the cats' for you. You can then just pass the MeetingConfiguration class to whatever you're initializing. You won't solve the problem of having multiple overloads, but at least all the overloads are in one place. –  Jun 16 '12 at 21:51
  • 7
    If you really write to all those repositories in this `MeetingService`, it must be doing an awful lot. What about breaking up the `MeetingService` into other services that only share keys? What I mean is, what is your unit of work here? If a meeting is complete after `meetingReposity.Save();` then fire an event and let all the others subscribe. – Davin Tryon Jun 16 '12 at 21:57
  • 1
    I think @dtryon's advice is far better. I'm not a fan of the idea of creating a "configuration" class just to hide that you in fact have lots of dependencies. That just makes the code far less clear but not any better organized. – Kirk Woll Jun 16 '12 at 22:05
  • Also agree with @dtryon (post an answer!) this huge number of constructor parameters is usually a sure sign that a class is doing too much and violating SRP – David Hall Jun 16 '12 at 22:23
  • I think a repository for the repositories is in order ...actually, you could use MEF and compose. – IAbstract Jun 16 '12 at 23:16

4 Answers4

4

Constructor overuse is just a symptom - it seems you are approximating a unit of work by having a "master" class that knows about the various elements of message persistence and plugs them into the overall save.

The downside is that each repository communicates its independence of the others by exposing a dedicated Save method; this is incorrect, though, as SaveMeeting explicitly states that the repositories are not independent.

I suggest identifying or creating a type that the repositories consume; this centralizes your changes and allows you to save them from a single place. Examples include DataContext (LINQ to SQL), ISession (NHibernate), and ObjectContext (Entity Framework).

You can find more information on how the repositories might work in a previous answer of mine:

Advantage of creating a generic repository vs. specific repository for each object?

Once you have the repositories, you would identify the context in which they would act. This generally maps to a single web request: create an instance of the common unit of work at the beginning of the request and hand it to all the repositories. At the end of the request, save the changes in the unit of work, leaving the repositories free to worry about what data to access.

This neatly captures and saves everything as a single unit. This is very similar to the working copy of your source control system: you pull the current state of the system into a local context, work with it, and save the changes when you're done. You don't save each file independently - you save them all at the same time as a discrete revision.

Community
  • 1
  • 1
Bryan Watts
  • 42,403
  • 15
  • 78
  • 85
  • Thank you for your help. I found your answer very useful. As for my design, it was built this way because I was trying to move from ActiverRecord to Repository. So implementing Unit of Work will be my next step. – k0stya Jun 22 '12 at 19:09
3

To expand a little bit on my comment above:

Since this question is directed towards how to manage repository dependencies, I have to assume that the MeetingService is managing some sort of persistent commit. In the past, when I have seen classes like MeetingService with that many dependencies, it is clear they are doing too much. So, you have to ask yourself, "what is my transaction boundary". In other words, what is the smallest commit that you can make that means that a meeting has been successfully saved.

If the answer is that a meeting is successfully saved after a call to meetingReposity.Save(); then that is all that MeetingService should be managing (for the commit).

Everything else is, essentially, a side effect of the fact that a meeting has been saved (notice that now we are speaking in the past tense). At this point, event subscription for each of the other repositories makes more sense.

This also has the nice effect of separating the logic in all of the conditions into subscriber classes that follow SRP to handle that logic. This becomes important when the logic of when the contact repository commits goes through a change, for example.

Hope this helps.

Davin Tryon
  • 62,665
  • 13
  • 135
  • 126
1

Each of the first three answers give important suggestions and ideas for dealing with the problem in the abstract. However, I may be reading too much into your example above, but this looks like a problem of too many aggregate roots, not too many dependencies per se. This has to do with either a lack in the persistence mechanism underlying your repository injection infrastructure, or a misconfiguration of the same.

Simply, the Contacts, Attendees, Notes, &c. should be composite properties of the Meeting itself (if only as links to separately managed Contact, &c. objects/data); therefore, your persistence mechanism should be saving them automatically.

Heeding Bryan Watts' adage that "constructor overuse is just a symptom," a couple of other possibilities:

  • Your persistence mechanism should be handling persistence of the Meeting graph automatically, and is either misconfigured or lacks the capability to do this (all three that Bryan suggests do this, and I would add DbContext (EF 4.1+)). In this case, there should really only be one dependency--IMeetingRepositoy--and it can handle atomic saves of the meeting and its composites itself.
  • SaveMeeting() is saving not just links to other objects (Contacts, Attendees, &c.) but is also saving those objects as well, in which case I would have to agree with dtryon that MeetingService and SaveMeeting() are doing far more than the names imply, and his mechanism could alleviate it.
Marc L.
  • 2,801
  • 1
  • 27
  • 38
0

Do you really need the repository functionality to be split into that many interfaces? Do you need to mock them separately? If not, you could have fewer interfaces, with more methods.

But let's assume your class really needs that many dependencies. In that case you could:

  • Create a configuration object (MeetingServiceBindings) that provides all the dependencies. You could have a single configuration object per whole module, not just single service. I don't think there's anything wrong with this solution.
  • Use a Dependency injection tool, like NInject. It's quite simple, you can configure your dependencies in code in one place and don't need any crazy XML files.
Martin Konicek
  • 33,336
  • 20
  • 82
  • 92
  • The OP is *already* using dependency injection. And the problem with these auxiliary classes is that you end up reusing them, as you suggest! That sounds great, except you end up reusing them in classes that don't have all the dependencies implied by the auxiliary class. This makes your true dependencies greatly obscured. – Kirk Woll Jun 16 '12 at 22:48
  • Good point about obscuring the dependencies. You should then rather create a configuration object per class, not the whole module. Note that "DI tool" is not the same as "DI". – Martin Konicek Jun 16 '12 at 22:51
  • 1
    Another solution is just to leave the code as is, if the class really has that many dependencies and constructor injection is preferred. – Martin Konicek Jun 16 '12 at 22:51
  • "If not, you could have fewer interfaces, with more methods." Fewer interfaces with more methods leads to a violation of the [Interface Segregation Principle](http://en.wikipedia.org/wiki/Interface_segregation_principle), so I vote against this. – Steven Jun 17 '12 at 12:16