Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I have a service endpoint initialized using DI. It is of the following style. This end point is used across the app.

 public class CustomerService : ICustomerService
{
    private IValidationService ValidationService { get; set; }
    private ICustomerRepository Repository { get; set; }

    public CustomerService(IValidationService validationService,ICustomerRepository repository)
    {
        ValidationService = validationService;
        Repository = repository;
    }


    public void Save(CustomerDTO customer)
    {
        if (ValidationService.Valid(customer))
            Repository.Save(customer);
    }

Now, With the changing requirements, there are going to be different types of customers (Legacy/Regular). The requirement is based on the type of the customer I have to validate and persist the customer in a different way (e.g. if Legacy customer persist to LegacyRepository).

The wrong way to do this will be to break DI and do somthing like

  public void Save(CustomerDTO customer)
    {
        if(customer.Type == CustomerTypes.Legacy)
        {
            if (LegacyValidationService.Valid(customer))
                LegacyRepository.Save(customer);
        }
        else
        {
            if (ValidationService.Valid(customer))
                Repository.Save(customer);
        }
    }

My options to me seems like

  1. DI all possible IValidationService and ICustomerRepository and switch based on type, which seems wrong.
  2. The other is to change the service signature to Save(IValidationService validation, ICustomerRepository repository, CustomerDTO customer) which is an invasive change.
  3. Break DI. Use the Strategy pattern approach for each type and do something like:

    validation= CustomerValidationServiceFactory.GetStratedgy(customer.Type);
    validation.Valid(customer)
    

    but now I have a static method which needs to know how to initialize different services.

I am sure this is a very common problem, What is the right way to solve this without changing service signatures or breaking DI?

share|improve this question
1  
I don't know if this directly solves your particular problem but I can recommend this book: manning.com/seemann it discusses DI indepth and is written by someone who knows his topic very well. He also discusses DI anti-patterns as well. I'm sure you'll find a lot of good stuff in here. –  Daniel Hollinrake Nov 19 '12 at 14:20
1  
Can you explain why you think that it is wrong to let CustomerService.Save(CustomerDTO customer) decide which repository to use? –  k3b Nov 19 '12 at 14:25
    
To do that either I have to initialize the repository inside the Save method, Which will make the CustomerService untestable. or inject a bunch of repositories in the constructor which is possible but ugly as I will have add a repository everytime there is a new type of customer. –  Pradeep Nov 19 '12 at 14:39
add comment

6 Answers

up vote 1 down vote accepted

If you have only two types of customers i would use the ugly version to let CustomerService.Save(CustomerDTO customer) decide which repository to use. Of course this violates the open-closed-priciple but obeys the keepItSimpleStupid (kiss).

If you really have several types of customers I would implement one customerservice for every type of customer and one customerservice on top that decides which spezialized service to use using a strategy pattern.

 > but now I have a static method which needs to know how to initialize different services.

It is not necessary that the strategy is a static method.

Instead of letting the strategy know every service you can reverse the dependency and let every spezialized service register at the stragey like this

 public class LegacyCustomerService : ICustomerService
 {
    LegacyCustomerService(IStrategy strategy)
    {
       strategy.register(this, typeof(LegacyCustomer));
    }
 }

The strategy uses a hash/dictionary from customer-type to service.

This is an open-closed-priciple solution that violates the keepItSimpleStupid (kiss) principle.

share|improve this answer
    
That works for me. I do have to deal with more than 2 types of object. [The code I used was just to illustrate the problem]. I know this will create a class explosion and violate KISS but that is a price I am willing to pay for extensibility. –  Pradeep Nov 20 '12 at 16:47
add comment

Inject your IValidationServices into your CustomerValidationServiceFactory. GetStrategy should not be static. Inject CustomerValidationServiceFactory into your CustomerService. Use as approproiate.

share|improve this answer
add comment

I wouldn't use a static method, but it seems like an IoC container (or simple DI depending on usage pattern) for your ICustomerService is appropriate. After all, if the customer dictates that the validation and repo both change together, that is the axis of change, not each of the members independently.

It should also hopefully cut down on number of combinations, making the code less complex.

share|improve this answer
add comment

If they're private and there's no logic behind the getters/setters (as it appears in your code example), there should be no reason for your class members to be properties:

private IValidationService ValidationService { get; set; }
private ICustomerRepository Repository { get; set; }

would be better served as:

private IValidationService ValidationService;
private ICustomerRepository Repository;

or even better yet, if they're only set in the constructor (again, as the code shows), make them readonly to declare intent and keep them invariant throughout the lifetime of the class:

private readonly IValidationService ValidationService;
private readonly ICustomerRepository Repository;
share|improve this answer
add comment

CustomerService is coupled to three interfaces (ValidationService, Repository and CustomerDTO) and basically orchestrates a validation before saving.

Best option is to take a step back, and consider different instances of CustomerService, one for each type of customer and already injected with the appropriated services. You can use the strategy pattern to switch between CustomerService instances, changing the caller of CS instances.

Using this approach, you don't need to change CustomerService, only the its caller. Such impact is consistent with the change: the new requirement suggests new type of customers, but does not change the way they are validaded and persisted.

share|improve this answer
add comment

Add a layer or two of abstraction.

Add an interface to wrap the constrained implementations together; something like:

public interface IValidatingRepository 
{
    public string Key {get; }
    public IValidationService ValidationService {get; }
    public ICustomerRepository Repository {get; }

    //personally, I would prefer to have this operation 
    // return bool or bool? or give some kind of feedback
    //Also, this could be genericized to TDto dto vs CustomerDTO customer
    void Save(CustomerDTO customer);
}

Implement IValidatingRepository, something like:

//this could alternately be an abstract base class, 
// if you wanted to require custom subclasses for each strategy 
public class ValidatingRepository : IValidatingRepository 
{  
    //these could be backed by readonly fields vs private setters if you prefer.
    public string Key {get; private set;}
    public IValidationService ValidationService {get; private set;}
    public ICustomerRepository Repository {get; private set;}

    public ValidatingRepository(
        string key, 
        IValidationService validationService, 
        ICustomerRepository repository
    )  
    { /* set properties / fields */}      

    public virtual void Save(CustomerDTO customer)
    {
        var validationService = ValidationService;
        var repository = Repository;
        if(customer == null || validationService == null || repository == null)
            return; //false? throw? some kind of feedback?

        //optional
        //if(!Preconditions())
        //  return; //false?

        if (validationService.Valid(customer))
            repository.Save(customer); //any return value from repository.Save(...)?

        //optional
        //return //Postconditions(customer);?
    }

    //optional
    protected virtual bool Preconditions(CustomerDTO customer)
    {
        if(customer.Type != Key)
            return false;  
        /* any other preconditions */
        return true;
    }

    //optional
    protected virtual bool Postconditions(CustomerDTO customer)
    {
        /* any postconditions */
    }
}

Alter the service implementation to have an instance of IValidatingRepository[] (or IEnumerable or other collection of your choice) rather than instances of IValidationService and ICustomerRepository.

Change the ctor to match[1].

public class CustomerService : ICustomerService
{
    private readonly IValidatingRepository[] ValidatingRepositorySet;

    public CustomerService(IValidatingRepository[] validatingRepositorySet)
    {
        ValidatingRepositorySet = validatingRepositorySet;
    }


    public void Save(CustomerDTO customer)
    {
        if(customer == null)
           return;

        var validatingRepositorySet = ValidatingRepositorySet;
        if(validatingRepositorySet == null)
           return;

        var key = customer.Type;
        var validatingRepository = validatingRepositorySet[key];
        if(validatingRepository == null)
            return;

        validatingRepository.Save(customer);

    }

Wherever you are constructing the CustomerService instance, compose together the correct validation service and repository pairs into a collection of IValidatingRepository instances and inject it into the service keyed by Customer.Type or whatever is applicable or works for your usage. If you add more service / repository pairs later no big deal. If you want to put in different / mock sets for testing you can. Etc.


EXTENDED

If your injection mechanism can't handle it or you prefer not to inject in collections directly for some reason, you can add another layer of abstraction by creating another interface to bundle the idea of multiple repositories and a specific corresponding validation service (and potentially other implementation specific concepts later); something like this:

//you could also make this generic for TDto, and generalize 
// away from CustomerDto to make this a more general purpose solution
public interface IValidatingRepositorySet 
{
   //IEnumerable<>, or whatever collection you like
   IValidatingRepository[] RepositorySet { get; set; } 

   //optional; you could move the strategy detection out of the service
   // and into the IValidatingRepositorySet implementation. 
   void Save(CustomerDTO customer);

}

And of course alter the service implementation to have an instance of IValidatingRepositorySet, and the ctor to take in an instance of it, and a default implementation somewhere, etc. Personally, I wouldn't do this unless I had one or more other things that applied to the set as a whole or otherwise belonged at a peer level to the set...such as moving the strategy selection logic out of the service (the optional Save method above).

NOTE: I wrote the code in the comment window on the fly; it's broad strokes only.

[1]Alternately, if for legacy support reasons you must retain the original ctor you can just wrap the passed in IValidationService & ICustomerRepository into a concrete default ValidatingRepository (you'll have to decide on a wildcard key, or have a special case implementation of ValidatingRepository that ignores key or supports an alternate key) that provides a simple implementation identical to the current functionality and set the IValidatingRepositorySet field / property to a new collection containing this instance. I would avoid this if you can, but it is an option.

share|improve this answer
    
What you proposed will work, but I would like to avoid passing a collection of validation services –  Pradeep Nov 20 '12 at 17:52
    
The public interface IValidatingRepositorySet option (under extended) to bundle the collection doesn't seem palatable either? –  Ed Hastings Nov 21 '12 at 19:31
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.