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?