# 我到底要不要跨层调用 Repo?

先说结论。我个人的观点是不推荐 interface、application 这两层直接调用 domain 的 Repository 的,在项目维护演进中容易腐化导致「业务逻辑泄露」。

# 举个例子

假设现在有一个用户的列表查询需求,如果我们直接在 interface 层调用对应的仓储接口:

// 接口层对外暴露的接口入口,目录可能是 interface/controller 或 adapter/driven/http
class UserController extends Controller {

    // 用户仓储接口
    @Resource private UserRepository userRepo;
    
    public UserListResponse getList() {
        List<User> userList = repo.list();
        // ... 将实体列表转成接口响应的 DTO 结构
        return response;
    }
}

在我们业务逻辑并不复杂的时候,这样写其实是没有任何问题的。它既符合 DDD 的规范,上层只依赖下层,也没有不合理的坏味道等问题。

# 随着业务演进

有一天,PO 来了个业务上来了个新需求:

临时用户如果已经过了有效期时刻,则不允许查询到该用户数据

这时候你可以有三个选择:

  1. 直接在现有的 Controller 逻辑中调整代码逻辑。
  2. 将现有的 repo 调用到 application 层,在该层实现对应的业务规则逻辑;Controller 中改为调用 application 的方法取数。
  3. 将现有的 repo 调整到 domain 层,在该层实现对应的业务规则逻辑;然后逐层向上暴露方法提供取数方法。

你会怎么选?

相信大家学过 DDD 的都会选择 2、3 的做法,因为这是「业务规则」,既然是业务知识,就应该存在于与业务相关的层。

你是否觉得 1 的选项有点无聊?你是否心里在想根本不可能选择 1 的做法?

你是对的,没有错!

但是这有个大前提,你们团队所有人都需要具备相应的 DDD 知识、你们团队需要具备完善的新人入职培训学习、甚至你们还需要有相关完备的 CR 流程机制。中间一旦某个环境出问题,代码腐化就从此开启。

最好的做法就是,从一开始就只给一个不至于复杂的选择。规则的理解门槛足够低,才能更容易去长久遵守。

# 再思考多一点

2、3 我们该怎么选呢?选 2 已经能满足本次需求,不会破坏规则;选 3 要写多一层「透传」的逻辑,但似乎更加「DDD」。

这里我给你分享一个经验:

你分析一下,同一个项目中在可以预见的未来(3-5 个月)中,是否会出现其他上下文模块中需要查询用户列表,并需要遵循这个业务规则来取数。如果有的话,我建议你选择将业务规则封装至 domain 层去。这样的好处是可以让你无需在 application 层编写多次相同的过滤逻辑,出现问题时的代码如下:

// 用户应用服务
class UserApplication {

    // 用户仓储接口
    @Resource private UserRepository userRepo;
    
    public List<User> list() {
        List<User> userList = repo.list();
        // ... 过滤已过有效期的临时用户
        return userList;
    }
}

// 组织架构应用服务
class OrgApplication {

    @Resource private UserRepository userRepo;

    public List<User> listUsersByOrgId(int orgId) {
        List<User> userList = repo.listByOrgId(orgId);
        // ... 过滤已过有效期的临时用户
        return userList;
    }
}