Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolve circular dependency with Dependency Injection

I have three classes which implement their interface.

Orders

public interface IOrderService
{
   //SomeOrderFunction()
}

public class OrderService: IOrderService
{
    //SomeOrderFunction();
}

Drivers

public interface IDriverService
{
   //SomeDriverFunction()
}

public class DriverService: IDriverService
{
    //SomeDriverFunction();
}

Plans -- Uses Driver and Plan services

public interface IPlanService
{
   //SomePlanFunction()
}

public class PlanService: IPlanService
{
    private readonly IOrderService _orderService;
    private readonly IDriverService _driverService;

    public PlanService(IDriverService driverService, IOrderService orderService)
    {
      _orderService = orderService;
      _driverService = driverService;
    }

    //PlanFunctionsThatUseServices();
}

The issue I now have though is that the Order and Driver service need to talk back to the plan service when either an Order or Driver gets changed, how would I go about doing this without having a circular dependency?

Edit: Specifically I've been looking at creating a fourth service that manages all the other ones such as the one explained in this article.

Breaking Dependency Cycles

What confuses me about this implementation is my Plan class, does my plan class implement both the new class AND the IPlan interface?

like image 457
Kelsey Abreu Avatar asked Sep 08 '26 15:09

Kelsey Abreu


2 Answers

via events.

create an event handler interface:

public interface IEventSubscriber<TEvent>
{
    void Handle(TEvent evt);
}

then define an event:

public class PlanCreated
{
    public int PlanId { get; set; }
    //and other properties.
}

Now let one of the other classes implement it:

public class DriverService : IDriverService, IEventSubscriber<PlanCreated>
{
    public void Handle(PlanCreated evt)
    {
        //handle it here.
    }
}

now, to be able to publish events you need another interface:

public interface IEventPublisher
{
    void Publish<TEvent>(TEvent evt);
}

Which can be invoked from your class:

public class PlanService: IPlanService
{
    private readonly IOrderService _orderService;
    private readonly IDriverService _driverService;

    public PlanService(IDriverService driverService, IOrderService orderService, IEventPublisher publisher)
    {
      _orderService = orderService;
      _driverService = driverService;
    }

    public void PlanFunctionsThatUseServices()
    {

       //business code....


      _publisher.Publish(new PlanCreated(){ Id = plan.Id } );
}

.. to invoke it we can use service location (implementation detail):

public class EventPublisher : IEventPublisher
{

    public EventPublisher(YourFavoriteContainer container)
    {
    }


    public void Publish<TEvent>(TEvent evt)
    {
        using (var scope = _container.BeginLifetimeScope())
        {
            var handlers = scope.ResolveAll<IEventSubscriber<TEvent>>();
            foreach (var handler in handlers)
            {
                //TODO: Handle exceptions=
                handler.Handle(evt);
            }
        }

    }
}

.. as a result you get low coupling between the classes.

like image 54
jgauffin Avatar answered Sep 11 '26 05:09

jgauffin


Inject a Func<IPlanService> instead of the IPlanService into the IOrderService and IDriverService. This 'breaks' the chain of instances that the container has to create.

public class OrderService {
    public OrderService(Func<IPlanService> planServiceFactory) {
        _planServiceFactory = planServiceFactory;
    }

    private readonly Func<IPlanService> _planServiceFactory;

    public void SomeOrderFunction() {
        _planServiceFactory().Notify(...);
    }
}
like image 27
Maarten Avatar answered Sep 11 '26 06:09

Maarten



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!