Resolving Dependencies based on request in Servicestack

Viewed 47

I have a Servicestack Api and i need suggestions \ ideas in injection the dependencies.

My Api needs to call appropriate dependency based on the request parameters

I have registered the dependencies as below


    public class AppHost : AppHostBase
    {
        //default code
    
        public override void Configure(Container container)
        {
            container.Register<ITravelManager>("Air", new AirTravelManager());
            container.Register<ITravelManager>("Road", new RoadTravelManager());
        }
    }

The service looks as :

public class TravelService:Service
{
    private readonly ITravelManager traveleManager;

    public TravelService(ILogService logService) 
    {
        
    }
    
    public TravelByAir Post(TravelByAirReq request)
    {
        traveleManager= ServiceStackHost.Instance.Container.ResolveNamed<ITravelManager >("Air");
        traveleManager.BooKTickets();
    }
    
    public TravelByRoad Post(TravelByRoadReq request)
    {
        traveleManager= ServiceStackHost.Instance.Container.ResolveNamed<ITravelManager >("Road");
        traveleManager.BooKTickets()
    }
}

My manager class looks as

public interface ITravelServiceManager 
{
     Tickets BooKTickets();
}
public class AirTravelManager
{
     Tickets BooKTickets()
     {
          ....
     }
}
public class SeaTravelManager
{
     Tickets BooKTickets()
     {
          ....
     }
}

Resolving traveleManager this way , looks like anti-pattern. Is there better approach \ pattern to resolve traveleManager with out using service locator.

1 Answers

I wouldn't register multiple named ITravelManager instances, instead I would register a single TravelServiceManager instance that determines which ITravelManager to return based on the request, e.g:

public override void Configure(Container container)
{
    container.Register(new TravelServiceManager(
        new AirTravelManager(), new RoadTravelManager()));
}

Where TravelServiceManager has a method to return the ITravelManager to use for that request, e.g:

public interface ITravelManager
{
    Tickets BooKTickets();
}
public class TravelServiceManager
{
    private readonly AirTravelManager air;
    private readonly RoadTravelManager road;

    public TravelServiceManager(AirTravelManager air, RoadTravelManager road)
    {
        this.air = air;
        this.road = road;
    }

    public ITravelManager GetTravelManager(IRequest req) => req.Dto switch
    {
        TravelByAirReq => air,
        TravelByRoadReq => road,
        _ => throw new NotSupportedException(
            $"Can't resolve {nameof(ITravelManager)} for request {req.Dto.GetType().Name}")
    };
}
public class AirTravelManager : ITravelManager
{
    public Tickets BooKTickets() => null;
}
public class RoadTravelManager : ITravelManager
{
    public Tickets BooKTickets() => null;
}

This way you resolve the TravelServiceManager as a normal dependency in your service:

public class TravelService : Service
{
    public TravelServiceManager TravelServiceManager { get; set; } 

    public object Post(TravelByAirReq request)
    {
        var travelManager = TravelServiceManager.GetTravelManager(base.Request);
        travelManager.BooKTickets();
        //...
    }        
}

But if you had more than a handful of ITravelManager's, I would remove the individual list of Request DTOs and use a custom attribute to annotate which TravelManager to use for which request, e.g:

[TravelManager("air")]
public class TravelByAirReq {}

[TravelManager("road")]
public class TravelByRoadReq {}

//...

Then use the metadata attribute on that request to determine which ITravelManager to use, e.g:

public class TravelServiceManager
{
    private Dictionary<string, ITravelManager> travelManagers;
    public TravelServiceManager(Dictionary<string, ITravelManager> travelManagers) => 
        this.travelManagers = travelManagers;

    public ITravelManager GetTravelManager(IRequest req)
    {
        var key = req.Dto.GetType().FirstAttribute<TravelManagerAttribute>()?.Name;
        if (key != null && travelManagers.TryGetValue(key, out var travelManager))
            return travelManager;

        throw new NotSupportedException(
            $"Can't resolve {nameof(ITravelManager)} for request {req.Dto.GetType().Name}");
    }

}
Related