Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mechanism for Dependency Injection to Provide the Most Specific Implementation of a Generic Service Interface

I feel like I played buzzword bingo with the title. Here's a concise example of what I'm asking. Let's say I have some inheritance hierarchy for some entities.

class BaseEntity { ... }
class ChildAEntity : BaseEntity { ... }
class GrandChildAEntity : ChildAEntity { ... }
class ChildBEntity : BaseEntity { ... }

Now let's say I have a generic interface for a service with a method that uses the base class:

interface IEntityService<T> where T : BaseEntity { void DoSomething(BaseEntity entity)... }

I have some concrete implementations:

class BaseEntityService : IEntityService<BaseEntity> { ... }
class GrandChildAEntityService : IEntityService<GrandChildAEntity> { ... }
class ChildBEntityService : IEntityService<ChildBEntity> { ... }

Assume I've registered these all with the container. So now my question is if I'm iterating through a List of BaseEntity how do I get the registered service with the closest match?

var entities = List<BaseEntity>();
// ...
foreach(var entity in entities)
{
    // Get the most specific service?
    var service = GetService(entity.GetType()); // Maybe?
    service.DoSomething(entity);
}

What I'd like to do is have a mechanism set up such that if an entity has a type of ClassA the method would find no service for the specific class and so would return BaseEntityService. Later if someone came along and added a registration for this service:

class ClassAEntityService : IEntityService<ChildAEntity> { ... }

The hypothetical GetService method would start providing the ClassAEntityService for the ClassA types without requiring any further code changes. Conversely if someone came along and just removed all the services except BaseEntityService then the GetService method would return that for all classes inheriting from BaseEntity.

I'm pretty sure I could roll something even if the DI container I'm using doesn't directly support it. Am I falling into a trap here? Is this an anti pattern?

EDIT:

Some discussion with @Funk (see below) and some additional Google searches those discussions made me think to look up has made me add some more buzzwords to this. It seems like I'm trying collect all the advantages of DI Containers, the Strategy Pattern and the Decorator Pattern in a type safe way and without using a Service Locator Pattern. I'm beginning wonder if the answer is "Use a Functional Language."

like image 316
Spencer Ruport Avatar asked Feb 02 '19 05:02

Spencer Ruport


3 Answers

So I was able to roll something that did what I needed.

First I made an interface:

public interface IEntityPolicy<T>
{
    string GetPolicyResult(BaseEntity entity);
}

Then I made a few implementations:

public class BaseEntityPolicy : IEntityPolicy<BaseEntity>
{
    public string GetPolicyResult(BaseEntity entity) { return nameof(BaseEntityPolicy); }
}
public class GrandChildAEntityPolicy : IEntityPolicy<GrandChildAEntity>
{
    public string GetPolicyResult(BaseEntity entity) { return nameof(GrandChildAEntityPolicy); }
}
public class ChildBEntityPolicy: IEntityPolicy<ChildBEntity>
{
    public string GetPolicyResult(BaseEntity entity) { return nameof(ChildBEntityPolicy); }
}

I registered each of them.

// ...
.AddSingleton<IEntityPolicy<BaseEntity>, BaseEntityPolicy>()
.AddSingleton<IEntityPolicy<GrandChildAEntity>, GrandChildAEntityPolicy>()
.AddSingleton<IEntityPolicy<ChildBEntity>, ChildBEntityPolicy>()
// ...

As well as registering a policy provider class that looks something like this:

public class PolicyProvider : IPolicyProvider
{
    // constructor and container injection...

    public List<T> GetPolicies<T>(Type entityType)
    {
        var results = new List<T>();
        var currentType = entityType;
        var serviceInterfaceGeneric = typeof(T).GetGenericDefinition();

        while(true)
        {
            var currentServiceInterface = serviceInterfaceGeneric.MakeGenericType(currentType);
            var currentService = container.GetService(currentServiceInterface);
            if(currentService != null)
            {
                results.Add(currentService)
            }
            currentType = currentType.BaseType;
            if(currentType == null)
            {
                break;
            }
        }
        return results;
    }
}

This allows me to do the following:

var grandChild = new GrandChildAEntity();
var policyResults = policyProvider
    .GetPolicies<IEntityPolicy<BaseEntity>>(grandChild.GetType())
    .Select(x => x.GetPolicyResult(x));
// policyResults == { "GrandChildAEntityPolicy", "BaseEntityPolicy" }

More importantly I can do this without knowing the particular subclass.

var entities = new List<BaseEntity> { 
    new GrandChildAEntity(),
    new BaseEntity(),
    new ChildBEntity(),
    new ChildAEntity() };
var policyResults = entities
    .Select(entity => policyProvider
        .GetPolicies<IEntityPolicy<BaseEntity>>(entity.GetType())
        .Select(policy => policy.GetPolicyResult(entity)))
    .ToList();
// policyResults = [
//    { "GrandChildAEntityPolicy", "BaseEntityPolicy" },
//    { "BaseEntityPolicy" },
//    { "ChildBEntityPolicy", "BaseEntityPolicy" }, 
//    { "BaseEntityPolicy" }
// ];

I expanded on this a bit to allow the policies to provide an ordinal value if necessary and added some caching inside GetPolicies so it doesn't have to construct the collection every time. I've also added some logic which allows me to define interface policies IUnusualEntityPolicy : IEntityPolicy<IUnusualEntity> and pick those up as well. (Hint: Subtract the interfaces of currentType.BaseType from currentType to avoid duplication.)

(It's worth mentioning that the order of List is not guaranteed so I have used something else in my own solution. Consider doing the same before using this.)

Still not sure if this is something that already exists or if there's a term for it but it makes managing entity policies feel decoupled in a way that's manageable. For example if I registered a ChildAEntityPolicy : IEntityPolicy<ChildAEntity> my results would automatically become:

// policyResults = [
//    { "GrandChildAEntityPolicy", "ChildAEntityPolicy", "BaseEntityPolicy" },
//    { "BaseEntityPolicy" },
//    { "ChildBEntityPolicy", "BaseEntityPolicy" }, 
//    { "ChildAEntityPolicy", "BaseEntityPolicy" }
// ];

EDIT: Though I haven't yet tried it, @xander's answer below seems to illustrate that Simple Injector can provide much of the behavior of the PolicyProvider "out of the box". There's still a slight amount of Service Locator to it but considerably less so. I'd highly recommend checking that out before using my half-baked approach. :)

EDIT 2: My understanding of the dangers around a service locator is that it makes your dependencies a mystery. However these policies are not dependencies, they're optional add-ons and the code should run whether or not they've been registered. With regard to testing, this design separates the logic to interpret the sum results of the policies and the logic of the policies themselves.

like image 78
Spencer Ruport Avatar answered Sep 19 '22 20:09

Spencer Ruport


With Simple Injector

If you happen to be using Simple Injector for DI duties, the container can help with this. (If you're not using Simple Injector, see "With Other DI Frameworks," below)

The functionality is described in the Simple Injector docs, under Advanced Scenarios: Mixing collections of open-generic and non-generic components.

You'll need to make a slight adjustment to your service interface and implementations.

interface IEntityService<T>
{
    void DoSomething(T entity);
}

class BaseEntityService<T> : IEntityService<T> where T : BaseEntity
{
    public void DoSomething(T entity) => throw new NotImplementedException();
}

class ChildBEntityService<T> : IEntityService<T> where T : ChildBEntity
{
    public void DoSomething(T entity) => throw new NotImplementedException();
}

The services are now generic, with a type constraint describing the least specific entity type they're able to handle. As a bonus, DoSomething now adheres to the Liskov Substitution Principle. Since the service implementations provide type constraints, the IEntityService interface no longer needs one.

Register all of the services as a single collection of open generics. Simple Injector understands the generic type constraints. When resolving, the container will, essentially, filter the collection down to only those services for which the type constraint is satisfied.

Here's a working example, presented as an xUnit test.

[Theory]
[InlineData(typeof(GrandChildAEntity), new[] { typeof(GrandChildAEntityService<GrandChildAEntity>), typeof(BaseEntityService<GrandChildAEntity>) })]
[InlineData(typeof(BaseEntity), new[] { typeof(BaseEntityService<BaseEntity>) })]
[InlineData(typeof(ChildBEntity), new[] { typeof(ChildBEntityService<ChildBEntity>), typeof(BaseEntityService<ChildBEntity>) })]
[InlineData(typeof(ChildAEntity), new[] { typeof(BaseEntityService<ChildAEntity>) })]
public void Test1(Type entityType, Type[] expectedServiceTypes)
{
    var container = new Container();

    // Services will be resolved in the order they were registered
    container.Collection.Register(typeof(IEntityService<>), new[] {
        typeof(ChildBEntityService<>),
        typeof(GrandChildAEntityService<>),
        typeof(BaseEntityService<>),
    });

    container.Verify();

    var serviceType = typeof(IEntityService<>).MakeGenericType(entityType);

    Assert.Equal(
        expectedServiceTypes,
        container.GetAllInstances(serviceType).Select(s => s.GetType())
    );
}

Similar to your example, you can add ChildAEntityService<T> : IEntityService<T> where T : ChildAEntity and UnusualEntityService<T> : IEntityService<T> where T : IUnusualEntity and everything works out...

[Theory]
[InlineData(typeof(GrandChildAEntity), new[] { typeof(UnusualEntityService<GrandChildAEntity>), typeof(ChildAEntityService<GrandChildAEntity>), typeof(GrandChildAEntityService<GrandChildAEntity>), typeof(BaseEntityService<GrandChildAEntity>) })]
[InlineData(typeof(BaseEntity), new[] { typeof(BaseEntityService<BaseEntity>) })]
[InlineData(typeof(ChildBEntity), new[] { typeof(UnusualEntityService<ChildBEntity>), typeof(ChildBEntityService<ChildBEntity>), typeof(BaseEntityService<ChildBEntity>) })]
[InlineData(typeof(ChildAEntity), new[] { typeof(ChildAEntityService<ChildAEntity>), typeof(BaseEntityService<ChildAEntity>) })]
public void Test2(Type entityType, Type[] expectedServiceTypes)
{
    var container = new Container();

    // Services will be resolved in the order they were registered
    container.Collection.Register(typeof(IEntityService<>), new[] {
        typeof(UnusualEntityService<>),
        typeof(ChildAEntityService<>),
        typeof(ChildBEntityService<>),
        typeof(GrandChildAEntityService<>),
        typeof(BaseEntityService<>),
    });

    container.Verify();

    var serviceType = typeof(IEntityService<>).MakeGenericType(entityType);

    Assert.Equal(
        expectedServiceTypes,
        container.GetAllInstances(serviceType).Select(s => s.GetType())
    );
}

As I mentioned before, this example is specific to Simple Injector. Not all containers are able to handle generic registrations so elegantly. For example, a similar registration fails with Microsoft's DI container:

[Fact]
public void Test3()
{
    var services = new ServiceCollection()
        .AddTransient(typeof(IEntityService<>), typeof(BaseEntityService<>))
        .AddTransient(typeof(IEntityService<>), typeof(GrandChildAEntityService<>))
        .AddTransient(typeof(IEntityService<>), typeof(ChildBEntityService<>))
        .BuildServiceProvider();

    // Exception message: System.ArgumentException : GenericArguments[0], 'GrandChildBEntity', on 'GrandChildAEntityService`1[T]' violates the constraint of type 'T'.
    Assert.Throws<ArgumentException>(
        () => services.GetServices(typeof(IEntityService<ChildBEntity>))
    );
}

With Other DI Frameworks

I've devised an alternate solution that should work with any DI container.

This time, we remove the generic type definition from the interface. Instead, the CanHandle() method will let the caller know whether an instance can handle a given entity.

interface IEntityService
{
    // Indicates whether or not the instance is able to handle the entity.
    bool CanHandle(object entity);
    void DoSomething(object entity);
}

An abstract base class can handle most of the type-checking/casting boilerplate:

abstract class GenericEntityService<T> : IEntityService
{
    // Indicates that the service can handle an entity of typeof(T),
    // or of a type that inherits from typeof(T).
    public bool CanHandle(object entity)
        => entity != null && typeof(T).IsAssignableFrom(entity.GetType());

    public void DoSomething(object entity)
    {
        // This could also throw an ArgumentException, although that
        // would violate the Liskov Substitution Principle
        if (!CanHandle(entity)) return;

        DoSomethingImpl((T)entity);
    }

    // This is the method that will do the actual processing
    protected abstract void DoSomethingImpl(T entity);
}

Which means the actual service implementations can be very simple, like:

class BaseEntityService : GenericEntityService<BaseEntity>
{
    protected override void DoSomethingImpl(BaseEntity entity) => throw new NotImplementedException();
}

class ChildBEntityService : GenericEntityService<ChildBEntity>
{
    protected override void DoSomethingImpl(ChildBEntity entity) => throw new NotImplementedException();
}

To get them out of the DI container, you'll want a friendly factory:

class EntityServiceFactory
{
    readonly IServiceProvider serviceProvider;

    public EntityServiceFactory(IServiceProvider serviceProvider)
        => this.serviceProvider = serviceProvider;

    public IEnumerable<IEntityService> GetServices(BaseEntity entity)
        => serviceProvider
            .GetServices<IEntityService>()
            .Where(s => s.CanHandle(entity));
}

And finally, to prove it all works:

[Theory]
[InlineData(typeof(GrandChildAEntity), new[] { typeof(UnusualEntityService), typeof(ChildAEntityService), typeof(GrandChildAEntityService), typeof(BaseEntityService) })]
[InlineData(typeof(BaseEntity), new[] { typeof(BaseEntityService) })]
[InlineData(typeof(ChildBEntity), new[] { typeof(UnusualEntityService), typeof(ChildBEntityService), typeof(BaseEntityService) })]
[InlineData(typeof(ChildAEntity), new[] { typeof(ChildAEntityService), typeof(BaseEntityService) })]
public void Test4(Type entityType, Type[] expectedServiceTypes)
{
    // Services appear to be resolved in reverse order of registration, but
    // I'm not sure if this behavior is guaranteed.
    var serviceProvider = new ServiceCollection()
        .AddTransient<IEntityService, UnusualEntityService>()
        .AddTransient<IEntityService, ChildAEntityService>()
        .AddTransient<IEntityService, ChildBEntityService>()
        .AddTransient<IEntityService, GrandChildAEntityService>()
        .AddTransient<IEntityService, BaseEntityService>()
        .AddTransient<EntityServiceFactory>() // this should have an interface, but I omitted it to keep the example concise
        .BuildServiceProvider();

    // Don't get hung up on this line--it's part of the test, not the solution.
    BaseEntity entity = (dynamic)Activator.CreateInstance(entityType);

    var entityServices = serviceProvider
        .GetService<EntityServiceFactory>()
        .GetServices(entity);

    Assert.Equal(
        expectedServiceTypes,
        entityServices.Select(s => s.GetType())
    );
}

Because of the casting involved, I don't think this is as elegant as the Simple Injector implementation. It's still pretty good, though, and the pattern has some precedent. It's very similar to the implementation of MVC Core's Policy-Based Authorization; specifically AuthorizationHandler.

like image 22
xander Avatar answered Sep 17 '22 20:09

xander


First thing that strikes me as odd is that you define

interface IEntityService<T> where T : BaseEntity { void DoSomething(BaseEntity entity)... }

instead of

interface IEntityService<T> where T : BaseEntity { void DoSomething(T entity)... }

while you still provide different implementations for each T.

In a well designed hierarchy DoSomething(BaseEntity entity) shouldn't have to change its functionality based on the actual (derived) type.

If this is the case, you could extract the functionality following the Interface segregation principle.

If the functionality really is that subtype dependent, perhaps the DoSomething() interface belongs on the types themselves.

If you want to change algorithms at runtime there's also the Strategy pattern, but even then the concrete implementations aren't meant to be changed that often (i.e. while iterating a list).

Without more information about your design and what you're trying to accomplish, it's hard to provide further guidance. Please ref:

  • Liskov substitution principle
  • Interface segregation principle
  • Strategy pattern

Do note Service Locator is considered an anti-pattern. A DI container's sole purpose should be to compose the object graph at startup (in composition root).

As for a good read, if you like to cook, there's Dependency Injection in .NET (Manning pub, 2nd ed coming out).


UPDATE

I don't want to change algorithms at runtime in my use case. But I do want it to be easy to swap out segments of business logic without touching the classes they operate on.

That's what DI is all about. Instead of creating services to manage all your business logic - which results in an Anemic Domain Model and seems to have generic variance working against you - it pays to abstract your volatile dependencies - those likely to change - behind and interface, and inject those into your classes.

The example below uses constructor injection.

public interface ISleep { void Sleep(); }

class Nocturnal : ISleep { public void Sleep() => Console.WriteLine("NightOwl"); }
class Hibernate : ISleep { public void Sleep() => Console.WriteLine("GrizzlyBear"); }

public abstract class Animal
{
    private readonly ISleep _sleepPattern;

    public Animal(ISleep sleepPattern)
    {
        _sleepPattern = sleepPattern ?? throw new NullReferenceException("Can't sleep");
    }

    public void Sleep() => _sleepPattern.Sleep();
}

public class Lion : Animal
{
    public Lion(ISleep sleepPattern)
        : base(sleepPattern) { }
}

public class Cat : Lion
{
    public Cat(ISleep sleepPattern)
        : base(sleepPattern) { }
}

public class Bear : Animal
{
    public Bear(ISleep sleepPattern)
        : base(sleepPattern) { }
}

public class Program
{
    public static void Main()
    {
        var nocturnal = new Nocturnal();
        var hibernate = new Hibernate();

        var animals = new List<Animal>
        {
            new Lion(nocturnal),
            new Cat(nocturnal),
            new Bear(hibernate)
        };

        var Garfield = new Cat(hibernate);
        animals.Add(Garfield);

        animals.ForEach(a => a.Sleep());
    }
}

Of course, we've barely scratched the surface, but it's invaluable for building maintainable "plug and play" solutions. Though it takes a mind shift, explicitly defining your dependencies will improve your code base in the long run. It allows you to recompose your dependencies when you start to analyze them, and by doing so you can even gain domain knowledge.


UPDATE 2

In your sleep example how would new Bear(hibernate) and new Lion(nocturnal) be accomplished using a DI Container?

The abstractions make the code flexible for change. They introducing seams in the object graph, so you can easily implement other functionality later on. At startup, the DI Container is populated and asked to build the object graph. At that time, the code is compiled, so there's no harm in specifying concrete classes if the backing abstraction is too vague. In our case, we want to specify the ctor argument. Remember, the seams are there, at this time we're merely constructing the graph.

Instead of auto wiring

container.Register( 
    typeof(IZoo), 
    typeof(Zoo));

We can do it by hand

container.Register( 
    typeof(Bear), 
    () => new Bear(hibernate));

Note the ambiguity comes from the fact that there are multiple ISleep sleepPatterns in play, so we need to specify one way or another.

How do I provide IHunt in Bear.Hunt and Cat.Hunt but not Lion.Hunt?

Inheritance will never be the most flexible of options. That's why composition is often favored, not to say you should drop every hierarchy, but be aware of friction along the way. In the book I mentioned there's an entire chapter on interception, it explains how to use the decorator pattern to dynamically decorate an abstraction with new capabilities.

In the end, the I want the container to choose the closest match in the hierarchy approach just doesn't sound right to me. Though it might seem convenient, I'd prefer to set the container up right.

like image 22
Funk Avatar answered Sep 21 '22 20:09

Funk