Varying services based on paramaters

Apr 18, 2014 at 7:02 AM
Edited Apr 18, 2014 at 7:03 AM
Hi All,

We have a service that performs our account level lookups. The problem is we accept registrations from our own and some selected partners.

The only thing that differentiates the account is a BrandId. So I looked at the multi-tenant solution found here : Multi-tenant IoC strategy

The service is a mess and we're looking at ways to simplify it. It started as a POC that unfortunately made it's way into production. Currently we do really horrible IF/ELSE type code for every one of our calls.

I'm new to IoC or DI, so I would appreciate it if someone could point me in the right direction. Based on the code below, is it possible?
public Account GetAccount(int brandId, string accountReference)
        {
            try
            {
                // Look if this account is the collection of our sites
                if (ManagedBrands.Contains(brandId))
                {
                    // Read from our database
                }
                else if (brandId = 123)
                {
                    // Call Bob's WCF service (exposed by another branch)
                }
                else if (brandId = 456)
                {
                    // Call 3'rd party WEB-API 
                }
                else
                {
                    // Call an extremely slow service we don't control
                    // This is rate-throttled and only to be used when the sky is falling
                }
            }
            catch (Exception e)
            {
                // Cry!
            }
        } 
All the sites here operate independently in dispersed geographic locations, so connectivity is an issue. We do have all the data eventually (maximum 15 minute delay) in our database but we cannot refuse a user who just created an account with our partners, they cannot log in.

We've also started creating better API / Service calls for our registrations but two of our partner refuses to invest time in changing their calls. Unfortunately, their accounts pay the majority of the bills so we can't just tell them to get knotted.

Hope this is descriptive enough!
Coordinator
Apr 18, 2014 at 9:09 AM
There are many different directions you can go with this, but here's one that comes to mind. You can give each brand its own IAccountSelector, as follows:
public interface IAccountSelector {
    bool AcceptsBrand(int brandId);
    Account GetAccount(string accountReference);
}
Now you can have an implementation per brand:
public class ManagedBrandsAccountSelector : IAccountSelector {
    public bool AcceptsBrand(int brandId) {
        return ManagedBrands.Contains(brandId);
    }

    public  Account GetAccount(string accountReference) {
        // Read from our database
    }
}

public class BobsAccountSelector : IAccountSelector {
    public bool AcceptsBrand(int brandId) {
        return brandId == 123;
    }

    public  Account GetAccount(string accountReference) {
        // Call Bob's WCF service (exposed by another branch)
    }
}

public class TerminatorAccountSelector : IAccountSelector {
    public bool AcceptsBrand(int brandId) {
        return true;
    }

    public  Account GetAccount(string accountReference) {
        // Call an extremely slow service we don't control
        // This is rate-throttled and only to be used when the sky is falling
    }
}
You can register these implementations as follows:
// Register the terminator last. Simple Injector will always maintain the given order.
container.RegisterAll<IAccountSelector>(
    typeof(ManagedBrandsAccountSelector),
    typeof(BobsAccountSelector),
    typeof(TerminatorAccountSelector));
This allows you to resolve the sequence of IAccountSelector implementations. Since you don't want the rest of the application to work with this sequence, you should hide this sequence behind an abstraction. This will typically be your original abstraction:
public interface IAccountProvider {
    Account GetAccount(int brandId, string accountReference);
}
And this can be implemented as follows:
public class AccountProvider : IAccountProvider {
    private readonly IEnumerable<IAccountSelector> accountSelectors;
    public AccountProvider(IEnumerable<IAccountSelector> accountSelectors) {
        this.accountSelectors = accountSelectors;
    }

    public Account GetAccount(int brandId, string accountReference) {
        var selector = this.accountSelectors.First(selector => selector.AcceptsBrand(brandId));
        try {
            return selector.GetAccount(accountReference);
        } catch (Exception e) {
            // Cry!
        }
    }
}
And this AccountProvider can simply be registered as follows:
container.Register<IAccountProvider, AccountProvider>();
Alternatively, you can also extract the terminator from the list, since it has special behavior and hook it up explicitly in the AccountProvider. You'll end up with an AccountProvider like this:
public class AccountProvider : IAccountProvider {
    private readonly IEnumerable<IAccountSelector> accountSelectors;
    public AccountProvider(IEnumerable<IAccountSelector> accountSelectors) {
        this.accountSelectors = accountSelectors;
    }

    public Account GetAccount(int brandId, string accountReference) {
        var selector = this.accountSelectors.FirstOrDefault(selector => selector.AcceptsBrand(brandId))
            ?? new TerminatorAccountSelector();
        try {
            return selector.GetAccount(accountReference);
        } catch (Exception e) {
            // Cry!
        }
    }
}
And if there is a lot of exception handling in the AccountProvider, can could extract that logic to a decorator:
public class AccountProvider : IAccountProvider {
    private readonly IEnumerable<IAccountSelector> accountSelectors;
    public AccountProvider(IEnumerable<IAccountSelector> accountSelectors) {
        this.accountSelectors = accountSelectors;
    }

    public Account GetAccount(int brandId, string accountReference) {
        // Nice and clean: no more exception handling here (makes this class easier to test).
        var selector = this.accountSelectors.FirstOrDefault(selector => selector.AcceptsBrand(brandId))
            ?? new TerminatorAccountSelector();
        return selector.GetAccount(accountReference);
    }
}

public class ExceptionHandlingAccountProviderDecorator : IAccountProvider {
    private readonly IAccountProvider  decoratee;
    public AccountProvider(IAccountProvider  decoratee) {
        this.decoratee = decoratee;
    }

    public Account GetAccount(int brandId, string accountReference) {
        try {
            return this.decoratee.GetAccount(brandId, accountreference);
        } catch (Exception e) {
            // Cry!
        }
    }
}
You can register this decorator as follows:
// using SimpleInjector.Extensions;
container.RegisterDecorator(typeof(IAccountProvider), typeof(ExceptionHandlingAccountProviderDecorator));
There are probably more solutions to this, but after seeing your code, this was the first thing that came to mind.
Marked as answer by dot_NET_Junkie on 4/18/2014 at 2:48 AM
Apr 18, 2014 at 10:21 AM
Dude, you are legendary! Thank you so much!!!