You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Clean架构下服务操作跨实体数据的调用选择:直接使用仓储还是依赖对应业务服务?

Clean架构下服务操作跨实体数据的调用选择:直接使用仓储还是依赖对应业务服务?

这个问题我刚摸Clean架构的时候也纠结过好一阵,太懂这种“到底该依赖谁”的迷茫了😅 其实核心判断标准非常明确,完全看你是否需要复用目标实体的核心业务规则,给你拆解两种最常见的场景:

优先依赖业务服务(ServiceA)的场景

如果ServiceB要添加的ObjA,需要遵循和ServiceA里完全一致的业务规则——比如你代码里ServiceA的AddAsync里做的ObjA entity = obj.Map();以及注释里的业务逻辑(比如字段校验、默认值填充、状态初始化这些),那必须依赖ServiceA

举个实际的例子:假设ServiceA的AddAsync里会自动给ObjA的CreateTime字段设为当前时间,还会校验Name字段不能为空。要是ServiceB直接调仓储,要么你得把这两段逻辑再抄一遍,要么就会漏掉,导致同一个ObjA的业务规则散落在两个地方。后期如果规则变了(比如CreateTime要改成UTC时间),你得同时改ServiceA和ServiceB,很容易漏改出Bug,完全违反了Clean架构里“业务规则要集中封装”的核心要求。

可以考虑直接用仓储的场景

只有当你能100%确定:ServiceB要添加的ObjA,不需要用到ServiceA里的任何业务规则,而且这个ObjA的创建逻辑是完全符合ObjA实体本身的核心规则的——这种情况才适合直接调仓储。

不过这里要提个Clean架构的关键细节:ObjA的核心业务规则其实不应该放在ServiceA里,而应该封装在ObjA实体类本身。比如把Map()和校验逻辑移到ObjA的静态方法里:

public class ObjA
{
    public string Name { get; private set; }
    public DateTime CreateTime { get; private set; }

    public static ObjA FromADTO(ObjADTO dto)
    {
        // 这里放校验、默认值填充等核心规则
        if (string.IsNullOrEmpty(dto.Name))
            throw new ArgumentException("Name不能为空");
        return new ObjA
        {
            Name = dto.Name,
            CreateTime = DateTime.UtcNow
        };
    }

    public static ObjA FromBDTO(ObjBDTO dto)
    {
        // 针对ServiceB的DTO转换,同样遵循ObjA的核心规则
        if (string.IsNullOrEmpty(dto.ObjAName))
            throw new ArgumentException("Name不能为空");
        return new ObjA
        {
            Name = dto.ObjAName,
            CreateTime = DateTime.UtcNow
        };
    }
}

这种情况下,不管ServiceA还是ServiceB,创建ObjA都用实体类的统一方法,然后直接调仓储也不会有规则不一致的问题——因为核心规则已经在实体里了,服务层只是做自己的业务逻辑。

最后给你个简单的决策口诀

  1. 先想:我要添加的ObjA,是否需要遵循ServiceA里的业务规则?
    • 是 → 依赖ServiceA
    • 否 → 再看ObjA的核心规则是不是已经封装在实体类里了?
      • 是 → 可以直接用仓储
      • 否 → 先把规则移到实体类里,再选仓储或ServiceA

其实没有绝对的“必须怎样”,但记住业务规则的封装性和一致性是Clean架构的核心,别让相同的规则散落在多个地方,这能帮你省掉后期超多的维护麻烦。

火山引擎 最新活动