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都用实体类的统一方法,然后直接调仓储也不会有规则不一致的问题——因为核心规则已经在实体里了,服务层只是做自己的业务逻辑。
最后给你个简单的决策口诀
- 先想:我要添加的ObjA,是否需要遵循ServiceA里的业务规则?
- 是 → 依赖ServiceA
- 否 → 再看ObjA的核心规则是不是已经封装在实体类里了?
- 是 → 可以直接用仓储
- 否 → 先把规则移到实体类里,再选仓储或ServiceA
其实没有绝对的“必须怎样”,但记住业务规则的封装性和一致性是Clean架构的核心,别让相同的规则散落在多个地方,这能帮你省掉后期超多的维护麻烦。




