Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditional dependency resolver on run-time (.net Core)

I have two classes PaymentGatewayFoo, PaymentGatewayBoo that both implements a common interface of IPaymentGateway:

interface IPaymentGateway { }

class PaymentGatewayFoo : IPaymentGateway { }
class PaymentGatewayBoo : IPaymentGateway { }

The client side request has an identifier who's response to determine which implementation to use:

public class OrderService
{
    private readonly IPaymentGateway _service;

    public void DoOrder(bool isFoo)
    {
        if (isFoo)
            //this._service should be resolved with PaymentGatewayFoo
        else
            //this._service should be resolved with PaymentGatewayBoo

        this._service.Pay();
    }
}

How do I resolve the proper implementation based on the client's request on run-time?

This question is not duplicate, its similar but its about two separate controllers (Even the answers suggested that the code didn't even needed the conditional dependency injection), in my case the conditional dependency is needed on run-time based on a client property value.

like image 865
Shahar Shokrani Avatar asked Sep 02 '19 14:09

Shahar Shokrani


1 Answers

There are several options here, but the two that to me are the most obvious are using a factory or the adapter pattern.

1. Use a factory

public class OrderService
{
    private readonly IPaymentGatewayFactory _factory;

    public void DoOrder(bool isFoo)
    {
        IPaymentGateway service = _factory.Create(isFoo);
        this._service.Pay();
    }
}

Where the factory can be:

public class PaymentGatewayFactory : IPaymentGatewayFactory 
{
    public PaymentGatewayFactory(PaymentGatewayFoo foo, PaymentGatewayBoo boo) {...}

    public IPaymentGateway Create(bool isFoo) =>
        isFoo ? this.foo : this.boo;
}

Pros and cons:

  • Downside of using a factory is that the consumer needs to be aware of two abstractions: the factory and the IPaymentGateway.

2. Use an adapter

public class OrderService
{
    private readonly IPayment _payment;

    public void DoOrder(bool isFoo)
    {
        _payment.Pay(isFoo);
    }
}

Where the adapter can be:

public class PaymentAdapter : IPayment
{
    public PaymentAdapter(PaymentGatewayFoo foo, PaymentGatewayBoo boo) {...}

    public void Pay(bool isFoo)
    {
        var service = isFoo ? this.foo : this.boo;

        service.Pay();
    }
}

Pros and cons:

  • Advantage of this is that the client only needs to be aware of a single abstraction.

Alternative implementations

As you noticed, in my factory and adapter, the implementations are injected directly. Not even by their abstractions, but by their concrete types. This might seem strange, but doing so is completely fine as long as the adapter and factory are part of the application's entry point (a.k.a. the Composition Root).

But other, more dynamic options can be used, such as:

  • Injecting a Func<PaymentType, IPaymentGateway> delegate to resolve the types.
  • Injecting a Dictionary<PaymentType, IPaymentGateway>.
  • Injecting a collection of IPaymentGateway implementations.
  • Injecting the container itself
  • Using dynamic filtering, as Armand suggests, but do note that is causes you to add the Identifier property to the interface, where it only exists for technical reasons. No consumer (other than the adapter or the factory) is interested in this identifier. It, therefore, doesn't belong to the interface. A better solution is to solve this problem in the Composition Root, possibly by marking the implementations with an attribute, for instance.
like image 179
Steven Avatar answered Oct 27 '22 15:10

Steven