【问题标题】:How to resolve circular dependencies inside of the service layer [duplicate]如何解决服务层内部的循环依赖关系[重复]
【发布时间】:2020-01-17 19:05:33
【问题描述】:

我知道其他人已经遇到过同样的问题,但我找不到任何令人满意的解决方案,所以我在这里寻求其他想法。

我的业务逻辑包含在这样的服务层中:

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
    }
}

这只是一个例子。在这种情况下,可能有一些解决方法可以避免服务相互依赖,但我过去遇到过多种其他情况,没有任何干净的解决方法。

如你所见,这两个服务相互依赖,依赖注入会因为循环依赖而失败。

现在我可以想出两种方法来解决这个问题:

解决方案 1

我可以解决需要它们的服务方法内部的服务依赖关系,而不是将服务依赖关系注入服务构造函数:

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
    }
}

问题:这使得单元测试更加困难,因为我需要注入一个模拟的IServiceProvider,它能够将我的ICourseService 解析到类构造函数中。在编写单元测试时也不是很清楚,每个服务方法需要哪些服务,因为这完全依赖于实现。

解决方案 2

服务方法可能要求 ICourseService 作为方法参数从控制器传入:

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

问题:现在我的控制器需要了解服务方法的实现细节:DeleteRoomAsync 需要一个 ICourseService 对象来完成它的工作。 我认为这不是很干净,因为DeleteRoomAsync 的要求将来可能会改变,但方法签名不应该。

您能想出其他更清洁的解决方案吗?

【问题讨论】:

  • 我建议你回顾一下当前循环设计的味道。如果不对代码如何使用这些依赖项进行适当的审查,就无法提供适当的解决方案。您最终只会得到经过试验和测试的标准变通办法,这些变通办法只治疗症状,而不是解决根本原因或气味。
  • 根据我的经验,试图避免循环设计会导致更多的代码异味。过去我尝试添加一个额外的服务层来解决这种情况。但是一旦一些服务方法变得更加复杂,这就会中断,你又回到了起点。这就是为什么这个问题不是要避免循环设计,而是要找到处理它的干净方法。

标签: c# asp.net asp.net-core dependency-injection


【解决方案1】:

在提供的示例中,我会重新考虑在这种情况下您是否真的存在服务间依赖关系:

  • 您的RoomService 实现中是否需要ICourseService 中包含的逻辑,还是只需要某些课程的信息?

    我会说后者,所以你真正的依赖可能是ICourseRepository 使用方法ICourseRepository.FindByRoom(Room room)

  • 您的CourseService 实现中是否需要IRoomService 中包含的逻辑,还是只需要现有房间?

    在这种情况下,IRoomRepository 就足够了。

但是,这并不总是那么容易,有时您确实需要在服务层中实现逻辑(验证等)。在这些场景中,最好尝试将该行为提取到共享类而不是复制它或创建循环依赖项。

【讨论】:

  • 正如我在问题中所写的那样,有可能的解决方法,例如您在此示例场景中描述的解决方法。但我正在寻找一种更通用的解决方案,它可以在其他服务中重用服务方法,因为这通常是避免代码重复和保持应用程序逻辑很好分离的一种非常好的方法。唯一的问题是,我必须解决依赖关系而不是每个服务而是每个方法,而不是避免循环依赖错误。但这带来了上述缺点。
【解决方案2】:

当然,最好的解决方案是避免循环依赖,但如果你真的被卡住了,你可以通过使用属性注入和RegisterInstance&lt;T&gt;(T t)(或等效的,如果你不使用 Autofac)来解决这个问题.

例如,如果您有一个相互依赖的FooService 类和一个BarService 类,您可以这样做:

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();
}

这会实例化两个服务,而不需要它们的依赖关系,然后将它们设置为彼此。当它们在 IoC 容器中注册时,它们的依赖关系已经完全建立。

请参阅我的Fiddle 以获取工作示例。

【讨论】:

  • 这是一个有趣的想法。但是我认为当我有许多范围内的服务都必须像您展示的那样手动实例化时,这不会很好地扩展。顺便说一句,我正在使用 ASP.Net Core 中包含的 ServiceProvider 实现,但我明白你的意思。
  • 没关系。经过一番研究,我单独提供了一个更好的答案。
【解决方案3】:

如果您的框架支持它,您可以将注入的依赖项作为Lazy&lt;T&gt; 提供,这会延迟解析并允许您拥有循环依赖项。

这些服务类可能如下所示:

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);
        }
    }
}

注册它们的代码不需要修改(至少 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();
}

查看DotNetFiddle 上的工作示例。

如果您的框架不支持像这样的惰性注入,您可能可以使用工厂(或任何其他延迟解析的模式)执行完全相同的操作。

另请参阅 this answer,它帮助我想出了这个解决方案。

【讨论】:

  • 我不喜欢这种设计。感觉更像是一个黑客。并且仍然可能导致问题,具体取决于使用依赖项的位置。
  • 我理解你的感受,这也是我最初分享的。当我发现这实际上是一个记录在案的模式时,我感到很惊讶(并且对它有了更多的信心)。例如,请参见 linklinklink
  • 我非常了解这种模式并且接触过很多次。这就是我评论它的原因。它不能解决循环依赖,具体取决于何时使用依赖(不要问我是怎么知道的:P)生活和学习。大声笑
  • 延迟分辨率不应该以这种方式使用。
  • @MarcusWichelmann 就像我说的我喜欢使用Lazy&lt;T&gt; 的延迟解决方案。在 CQRS 中经常使用它,但试图用它来打破循环依赖是相当麻烦的。好吧,如果它适合您,那么您可以选择将其投入生产。
猜你喜欢
  • 1970-01-01
  • 2014-09-06
  • 1970-01-01
  • 1970-01-01
  • 2021-04-26
  • 1970-01-01
  • 2018-10-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多