不使用CQRS时过滤标记删除的聚合体该由哪一层承担职责
这个问题的核心判断标准是你的业务对「查询已停用聚合」的需求频率,没有绝对的通用答案,两种方案分别适配不同场景:
如果绝大多数业务场景仅需要返回启用状态的聚合
优先把软删除过滤逻辑放在仓储(持久化)层:
- 符合仓储的封装原则:软删除是聚合的基础状态规则,作为聚合的持久化访问入口,仓储天然应该对上层屏蔽软删除的实现细节,避免上层每次调用查询都重复写过滤逻辑,大幅降低漏过滤的bug概率
- 扩展性也可以得到保障:如果有少数管理端场景需要查询已停用聚合,只需要在仓储方法上加一个可选参数即可,比如:
public List<Article> allArticlesByUserId(long userId, boolean includeDeactivated) { String query = "select article from Article article where article.userId = :userId " + (includeDeactivated ? "" : "and article.activated = true"); // 其余查询逻辑 }
默认不传的情况下只查启用数据,特殊场景按需传参即可,完全不需要改动上层逻辑。
如果多业务端频繁需要灵活切换是否查询停用聚合
可以把过滤能力向上透出到应用层甚至API层:
- 比如前台C端默认只返回启用内容,后台运营端需要支持查看全部/仅启用/仅停用内容,这种需求频繁出现的话,直接在API层透出
onlyActivated参数会更灵活 - 但要注意:过滤的具体实现逻辑依然要收口在仓储层,不要在应用层、Controller层做全量查询后再内存过滤,不仅性能差,还会导致逻辑分散难以维护。
示例优化建议
你给出的两个示例其实都有可优化的点:
- 第一个示例在Controller层做分支调用两个不同的应用服务方法,完全没有必要,直接把
onlyActivated参数透传给同一个应用服务方法,再下传给仓储即可,后续新增过滤参数也不会出现大量分支代码 - 第二个示例把过滤条件写死为只查停用数据,相当于完全屏蔽了查询启用数据的可能性,后续有需求改动必须调整仓储方法,扩展性很差。
通用最佳实践
- 所有软删除相关的过滤逻辑统一收口到仓储层,上层仅按需传递参数
- 仓储查询方法默认仅返回启用状态的聚合,可选参数支持包含已停用聚合
- 应用层根据用例的受众决定是否透出参数到API:面向普通用户的用例隐藏参数,面向运营的管理端用例按需透出参数
内容的提问来源于stack exchange,提问作者chytonpide
相关产品推荐
相关产品推荐

