5

I know that others already had this very same issue, but I cannot find any statisfying solution, so I'm asking here for other ideas.

My business logic is contained in a service layer like that:

public class RoomService : IRoomService
{
    private readonly IRoomRepository _roomRepository;
    private readonly ICourseService _courseService;

    public RoomService(IRoomRepository roomRepository, ICourseService courseService)
    {
        _roomRepository = roomRepository ?? throw new ArgumentNullException(nameof(roomRepository));
        _courseService = courseService ?? throw new ArgumentNullException(nameof(courseService));
    }

    public Task DeleteRoomAsync(string id)
    {
        // Check if there are any courses for this room (requires ICourseService)
        // Delete room
    }
}

public class CourseService : ICourseService
{
    private readonly ICourseRepository _courseRepository;
    private readonly IRoomService _roomService;

    public CourseService(ICourseRepository courseRepository, IRoomService roomService)
    {
        _courseRepository = courseRepository ?? throw new ArgumentNullException(nameof(courseRepository));
        _roomService = roomService ?? throw new ArgumentNullException(nameof(roomService));
    }

    public Task GetAllCoursesInBuilding(string buildingId)
    {
        // Query all rooms in building (requires IRoomService)
        // Return all courses for these rooms
    }
}

This is just an example. There might be workarounds to avoid that the services depend on each other in this case, but I had multiple other situations in the past, where there wasn't any clean workaround.

As you can see, these two services depend on each other and dependency injection will fail because of the circular dependency.

Now I can imagine two ways to resolve this:

Solution 1

I could resolve the service-dependencies inside of the service methods that require them instead of injecting the service dependencies into the service constructor:

public class RoomService : IRoomService
{
    private readonly IRoomRepository _roomRepository;
    private readonly IServiceProvider _serviceProvider;

    public RoomService(IRoomRepository roomRepository, IServiceProvider serviceProvider)
    {
        _roomRepository = roomRepository ?? throw new ArgumentNullException(nameof(roomRepository));
        _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
    }

    public Task DeleteRoomAsync(string id)
    {
        ICourseService courseService = _serviceProvider.GetRequiredService<ICourseService>();

        // Check if there are any courses for this room (requires ICourseService)
        // Delete room
    }
}

Problem: This makes unit testing harder because I need to inject a mocked IServiceProvider that is able to resolve my ICourseService into the class constructor. Also it's not very clear when writing the unit tests, which services are required by each service method because that's completely implementation dependant.

Solution 2

The service method could require that the ICourseService is passed in from the controller as a method parameter:

public Task DeleteRoomAsync(ICourseService courseService, string id)
{
    // Check if there are any courses for this room (requires ICourseService)
    // Delete room
}

Problem: Now my controller needs to know about an implementation detail of the service method: DeleteRoomAsync requires an ICourseService object to do it's work. I think that's not very clean because the requirements of DeleteRoomAsync might change in future, but the method signature should not.

Can you think of any alternative, cleaner solutions?

Marcus Wichelmann
  • 762
  • 1
  • 6
  • 18
  • 2
    I suggest you review that current cyclic design smell. Without a proper review of how the code uses those dependencies, a proper solution cannot be provided. You would only end up with the standard tried and tested workarounds that only treat the symptoms and not address the root cause or the smell. – Nkosi Sep 17 '19 at 00:08
  • 2
    In my experience trying to avoid the cyclic design results in even more code smell. I tried adding an additional service layer to fix this case in the past. But as soon as some service methods get more complicated, this will break and you're back to the start. That's why this question is not about avoiding the cyclic design but finding clean ways to deal with it. – Marcus Wichelmann Sep 17 '19 at 09:58

3 Answers3

3

If your framework supports it, you can provide your injected dependencies as a Lazy<T> which defers resolution and allows you to have circular dependencies.

Here's what those service classes might look like:

class FooService : IFooService
{
    protected Lazy<IBarService> _bar;

    public FooService(Lazy<IBarService> bar)
    {
        _bar = bar;
    }

    public void DoSomething(bool callOtherService)
    {
        Console.WriteLine("Hello world. I am Foo.");
        if (callOtherService)
        {
            _bar.Value.DoSomethingElse(false);
        }
    }

}

class BarService : IBarService
{
    protected Lazy<IFooService> _foo;

    public BarService(Lazy<IFooService> foo)
    {
        _foo = foo;
    }
    public void DoSomethingElse(bool callOtherService)
    {
        Console.WriteLine("Hello world. I am Bar.");
        if (callOtherService)
        {
            _foo.Value.DoSomething(false);
        }
    }
}

The code that registers them does not require modification (at least not with Autofac):

public static IContainer CompositionRoot()
{
    var builder = new ContainerBuilder();
    builder.RegisterType<FooService>().As<IFooService>().SingleInstance();
    builder.RegisterType<BarService>().As<IBarService>().SingleInstance();
    builder.RegisterType<Application>().SingleInstance();
    return builder.Build();
}

See a working example on DotNetFiddle.

If your framework does not support lazy injection like this, you can probably do the exact same thing using a factory (or any other pattern that defers resolution).

See also this answer which helped me come up with this solution.

John Wu
  • 50,556
  • 8
  • 44
  • 80
  • I am not a fan of this design. Feels more like a hack. And could still potentially cause issues depending on where the dependency is used. – Nkosi Sep 17 '19 at 00:30
  • I understand your feelings, which I initially shared. I was surprised (and gained a little more confidence in it) when I found this is actually a documented pattern. See [link](https://learn.microsoft.com/en-us/previous-versions/msp-n-p/dn507482(v=pandp.30)?redirectedfrom=MSDN) and [link](https://stackoverflow.com/a/41857656/2791540) and [link](https://stackoverflow.com/a/36196865/2791540), for example. – John Wu Sep 17 '19 at 00:36
  • I know the pattern very well and have been exposed to it a lot. That is why i commented about it. It does not solve the cyclic dependency depending on when the dependency is uses (don't ask me how I know :P) live and learn. lol – Nkosi Sep 17 '19 at 00:36
  • deferred resolution was not meant to be used in this manner. – Nkosi Sep 17 '19 at 00:39
  • I understand. I think there may be some unstated NFRs in your question. Anyway, if we're trying to use things only as intended or as suggested by best practices, I think we're back to the answer of "get rid of the circular dependencies." Best of luck! – John Wu Sep 17 '19 at 00:44
  • I like this approach. I can use [this trick](https://stackoverflow.com/a/45775657/5495384) to do this using the default DI container so that I can require something like a `ILateDependency` in the constructor. @Nkosi Could you please give us any example in which case this would break in your opinion? Of course it will not work when I consume the dependency inside of the construction code. But that's not something I'd do in service constructors anyway. – Marcus Wichelmann Sep 17 '19 at 10:07
  • @MarcusWichelmann Call any dependency member from in the subject class and watch the stack overflow. This answer works around it by using the bool flag to try and avoid the cycle. (hack) IMO this is not cleaner as now you have to remember to make sure to set that flag when invoking any member that uses the dependency. – Nkosi Sep 17 '19 at 10:25
  • @Nkosi Ah, I see what you mean. What I liked was the idea of using Lazy. That's great. The bool thing isn't really relevant to me, because I'd never build a construct where two methods will call each other like that. That's bad, I agree. – Marcus Wichelmann Sep 17 '19 at 10:30
  • 1
    @MarcusWichelmann Like i said I love the deferred resolution using `Lazy`. Use it a lot in CQRS, but trying to use it to break cyclic dependencies is rather hacky. Well, if it works for you then the choice is yours to put this into production. – Nkosi Sep 17 '19 at 10:34
2

The best solution is to avoid circular dependencies, of course, but it you're truly stuck, you can work around the issue by using property injection and RegisterInstance<T>(T t) (or its equivalent, if you're not using Autofac).

For example, if you have a FooService class and a BarService class that depend on each other, you can do this:

public static IContainer CompositionRoot()
{
    var foo = new FooService();
    var bar = new BarService();
    foo.Bar = bar;
    bar.Foo = foo;

    var builder = new ContainerBuilder();
    builder.RegisterInstance<IFooService>( foo );
    builder.RegisterInstance<IBarService>( bar );
    builder.RegisterType<Application>().SingleInstance();
    return builder.Build();
}

This instantiates both services without their dependencies, and then sets them to each other afterward. By the time they are registered with the IoC container, their dependencies are completely set up.

See my Fiddle for a working example.

John Wu
  • 50,556
  • 8
  • 44
  • 80
  • That's an interesting idea. But I don't think this would scale well when I have many scoped services which all have to be instanced manually like you've shown. BTW I'm using the `ServiceProvider` implementation included in ASP.Net Core, but I see what you mean here. – Marcus Wichelmann Sep 16 '19 at 23:58
  • 1
    That's okay. After a little research I provided a much better answer separately. – John Wu Sep 17 '19 at 00:16
1

In provided examples, I would re-consider if you really have inter-service dependencies in those kind of situations:

  • Do you need logic contained in ICourseService in your RoomService implementation, or do you only need information from certain courses?

    I would say that the latter one, so your real dependency could be ICourseRepository with a method ICourseRepository.FindByRoom(Room room).

  • Do you need logic contained in IRoomService in your CourseService implementation, or do you only need existing rooms?

    In this case, IRoomRepository could be enough.

However, it isn't always that easy and sometimes you really require logic implemented in Service layer, (validations, etc.). Trying to extract that behavior to shared classes rather than duplicating it or creating circular dependencies as mentioned can be preferrable in those scenarios.

eduherminio
  • 1,514
  • 1
  • 15
  • 31
  • As I've written in the question, there are possible workarounds like the ones you described in this example scenario. But I'm searching for a more generic solution that enables the re-use of service methods inside of other services because that's often a very nice way to avoid code duplication and keep application logic nicely separated. The only issue is, that I'd have to resolve the dependencies not per-service but per-method instead to avoid the circular dependency error. But this comes with above mentioned disadvantages. – Marcus Wichelmann Sep 17 '19 at 00:04