You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于CQRS的Todo应用DDD权限校验分层与实现位置咨询

嘿,针对你的CQRS Todo应用权限校验问题,我结合DDD和CQRS的最佳实践来给你拆解建议:

一、DDD分层中的权限职责归属

你的权限规则本质是业务规则(用户仅能操作同组Todo),所以不同分层的职责要明确:

  • 领域层:这是核心规则的归宿。你应该在Todo实体中封装权限校验逻辑,比如添加CanBeAccessedBy(User user)或CanBeUpdatedBy(User user)这样的方法,确保无论哪个上层调用领域实体,都必须遵守这个规则。比如当尝试更新一个Todo时,实体自身可以判断操作的用户是否属于同组,直接抛出业务异常如果权限不足。这样能保证业务规则的一致性,不会因为上层调用的不同而被绕过。
  • 应用层:作为协调层,应用层负责触发权限校验。比如在命令/查询处理器中,先获取当前用户的组信息,再调用领域层的校验方法,或者做前置的条件过滤(比如只处理用户同组的请求)。它是领域层和基础设施层的桥梁,但不应该承载核心业务规则。
  • 基础设施层:这层只做辅助性的权限优化,比如在查询数据库时直接带上GroupId = 当前用户GroupId的过滤条件,提升性能。但绝对不能用这个替代领域层的校验——因为如果有其他方式绕过数据库查询(比如直接操作内存中的实体),权限规则就会失效。

二、CQRS架构下的具体放置建议

CQRS把读写操作分开,所以要分别针对命令端(Create/Update)和查询端(Read)来安排:

命令端(Create/Update操作)

  • 优先放在命令处理器中:命令处理器是业务操作的入口,在执行命令前做权限校验是最合理的。比如处理CreateTodoCommand时,先拿到当前用户的GroupId,要么强制命令中的TodoGroupId与用户组一致(甚至可以直接从用户信息中获取GroupId,不让客户端传入,避免篡改),要么调用Todo.CanBeCreatedBy(user)做校验。如果权限不通过,直接返回错误,不进入领域逻辑。
  • 实体层做兜底校验:即使命令处理器漏了校验,实体自身也要保证规则被执行。比如Todo的构造函数可以接收用户的GroupId,确保创建的Todo属于用户所在组;更新方法也可以检查操作用户的组是否与Todo组匹配,防止非法修改。
  • 不建议放在控制器(动作)中:控制器只是API的入口,负责请求参数解析和响应返回。如果把权限校验放在这里,会导致业务逻辑分散到入口层,而且如果有其他命令触发方式(比如消息队列),就会绕过控制器的校验,出现权限漏洞。控制器只适合做基础的参数合法性校验(比如必填项是否为空)。

查询端(Read操作)

  • 放在查询处理器中:这是最适合的位置。处理查询请求时,查询处理器直接在查询逻辑中加入用户组的过滤条件。比如处理GetTodoListQuery时,执行SQL:SELECT * FROM Todo WHERE GroupId = @UserGroupId;处理GetTodoByIdQuery时,SQL要同时校验TodoId和GroupId,确保返回的Todo属于用户所在组。
  • 性能优先,兼顾规则:查询端通常更注重性能,所以直接在数据库层面过滤是最优解,但要确保查询处理器是读操作的唯一入口,避免其他绕过方式。如果需要更严格的校验,也可以在查询结果返回前,用领域层的CanBeAccessedBy方法再次确认,但这可能会带来额外的性能开销,需要权衡。

举个贴合你场景的例子

比如用户1(GroupId=1)请求获取Todo3(GroupId=2):

  1. 查询处理器拿到用户的GroupId=1,执行SQLSELECT * FROM Todo WHERE TodoId=3 AND GroupId=1,结果为空,返回“权限不足”的响应。
    再比如用户3(GroupId=2)创建Todo:
  2. 命令处理器拿到用户的GroupId=2,直接将命令中的TodoGroupId设为2(不让客户端传入,避免篡改),调用Todo.Create(title, desc, 2),实体构造函数确保GroupId有效,完成创建。

内容的提问来源于stack exchange,提问作者Furkan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 13:52:37