目录

Repository + Service 层常见循环依赖问题

Repository + Service 层常见循环依赖问题

一、典型场景:Repository ↔ Service 互相调用

先给你一个你一定见过(或未来一定会遇到)的例子:

public class UserService
{
    private readonly IUserRepository _userRepository;
    private readonly IOrderService _orderService;

    public UserService(IUserRepository userRepository, IOrderService orderService)
    {
        _userRepository = userRepository;
        _orderService = orderService;
    }
}

public class OrderService
{
    private readonly IOrderRepository _orderRepository;
    private readonly IUserService _userService;

    public OrderService(IOrderRepository orderRepository, IUserService userService)
    {
        _orderRepository = orderRepository;
        _userService = userService;
    }
}

典型循环依赖链:

  • UserService 要 OrderService
  • OrderService 要 UserService

DI 容器直接炸了:
“你要他,他要你,你俩自己玩吧。”

武侠理解

  • 用户掌门(UserService)练功需要订单掌门(OrderService)
  • 订单掌门练功需要用户掌门

两个掌门互相“依赖内功”,没有谁能先修炼 → 死循环

二、为什么 Repository ↔ Service 很容易循环依赖?

因为很多新人误把 Repository 当成 “迷你 Service”。

例如:

public interface IUserRepository 
{
    User GetUserDetail(long id);
}

然后在 Repository 中调用 Service:

public class UserRepository : IUserRepository
{
    private readonly IUserService _userService;
}

Repository 层应该很纯粹,只负责数据访问,不应该依赖 Service!!!

只要 Repository 调 Service,Service 再调 Repository,很容易出现:

Repository → Service → Repository → Service → …

三、为什么会写出循环依赖?

① 职责分离不清晰

Service 本应负责业务,Repository 负责数据访问,但开发者把“计算逻辑”放到了 Repository 中。

② Service 层互相调用太多

例如:

  • 用户服务要查订单
  • 订单服务要查用户
  • 商品服务也要查用户

久而久之互相形成网状依赖。

③ Repository 被当工具类使用

错误示例:

_userRepository.CheckUserPermission(...)

Repository 永远不应该做权限校验、业务判断!

四、如何解决

方法一:分离查询与业务

把查询从 Service 拆出去:

新建 QueryService(或 DomainQuery)专门用于查询:

UserService     → 处理业务逻辑
UserQuery       → 处理查询

OrderService    → 处理业务逻辑
OrderQuery      → 处理查询

所有 Service 需要用户信息时 → 调 Query 而不是调 Service

示例:

public class UserQuery : IUserQuery
{
    private readonly IUserRepository _userRepository;
}

这样 OrderService 只依赖 UserQuery,不依赖 UserService,就不会形成循环。

方法二:事件通知(避免主动调用)

例如:

  • 订单创建后需要更新用户积分
  • 不要让 OrderService 调 UserService
  • 而是让 OrderService 发布事件
public class OrderCreatedEvent : INotification
{
    public long UserId { get; set; }
    public decimal Amount { get; set; }
}

UserService 监听:

public class OrderEventHandler : INotificationHandler<OrderCreatedEvent>
{
    public Task Handle(OrderCreatedEvent e)
    {
        // 更新积分
    }
}

解耦 + 可扩展 + 无循环依赖

方法三:Factory(工厂注入,小范围解耦)

例如只某些业务流程中需要临时创建 service:

public class OrderService
{
    private readonly Func<IUserService> _userServiceFactory;
}

取到的时候才创建,不会循环。

方法四:Domain Service + Application Service

这是 DDD 的标准做法:

Application Service

  • 负责 orchestrate(流程编排)
  • 调用多个 Repository
  • 不做领域逻辑

Domain Service

  • 专注某个纯业务逻辑
  • 永远不依赖 ApplicationService

这样:

ApplicationService → DomainService → Repository

没有任何循环可能。

五、企业级典型重构示例

❌ 错误示例(循环依赖):

UserService → OrderService  
OrderService → UserService

✔ 正确改法:

UserService       → UserRepository
OrderService      → OrderRepository
OrderService      → UserQuery   (只读查询,不依赖业务)
UserQuery         → UserRepository

Service 不再互相调用,循环链被斩断。

六、总结(循环依赖的判断口诀)

不要让:

  • Repository 调 Service
  • Service A 调 Service B,再反过来 B 调 A
  • Service 做查询
  • Repository 做业务

要做到:

  • Service 只做业务逻辑
  • Query 专门负责查询
  • Repository 只做数据访问
  • 事件而不是主动调用