V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
skinny
V2EX  ›  问与答

在 DDD 或 Clean Architecture 里,请问数据验证代码该放在哪层?是用异常还是返回值?

  •  
  •   skinny · 2022-06-17 21:38:19 +08:00 · 1104 次点击
    这是一个创建于 924 天前的主题,其中的信息可能已经有所发展或是发生改变。

    背景:假设有一个 API 项目用的是 aspnetcore ,然后项目各个功能拆分成独立的组件库了(比如 identity 、user profiles 、sessions 、posts ),都有各自独立的接口,UI 层( API )需要哪个就引用哪个库配置哪个库。

    比如有一个创建用户的接口,首先框架会自动基于 Model 属性和数据类型对请求数据做基本的数据验证,比如是不是一个有效的 int 、Guid 、Email 等等。

    假如接下来我要验证这个电子邮件是不是符合某些规则,然后是不是唯一的没有被别人注册使用,这个逻辑我该在组件的 Service 里做还是数据存储层 Repository 里做,还是就在 Controller 那验证?

    我搜了下外网的回答,各种观点的都有,总结下来就是都觉得自己的是好的,别的都不建议,然后都有一点点道理。

    有的说应该确保从 Repository 存进和取出的数据都应该是有效的,否则世界就会变得太复杂,所以在 Repository 里验证;(到底有没有必要再在这里做一次验证?)

    有的说应该在 Entity 或 Model 做(里面还分成很多派系);

    有的说在 Service 做;

    有的在 Specification ;有的在 Command ;有的在 Event ;

    有的说在最开始创建或修改的地方做(通常是 Controller 那里);

    还有,数据无效(比如有非法字符,或这个电子邮件已经被使用)该使用异常还是返回值?

    有的观点是如果大量数据需要验证,使用异常会打断流程,但如果我在那层做完全部验证在把这些问题整合抛出一个异常呢?而且接口返回值也不需要再套几层对象包装。

    比如我觉得

    Task<User> Repository.AddAsync(User user)
    

    就比

    Task<IdentityResult> Repository.AddAsync(User user)
    

    清晰简单,我也不用考虑这个 IdentityResult 怎么来兼容其它类型的操作,外面的那层也可以不用套上这个返回值类型的枷锁。

    为什么我会需要考虑这个 IdentityResult 怎么来兼容其它类型的操作,比如我有一个接口,给用户添加一个 Claim ,然后返回完整的信息而不需要再查询一次,接口为这样:

    Task<UserClaim> AddClaimAsync(User user, Claim claim)
    

    如果改成返回 IdentityResult 就需要给 IdentityResult 增加属性,但是大部分时候是不需要这个属性的。

    你们会怎么做的?

    4 条回复    2023-03-01 11:40:54 +08:00
    chihiro2014
        1
    chihiro2014  
       2022-06-17 21:46:58 +08:00   ❤️ 1
    在入口 Controller 做验证不行么?毕竟 Dao 层取出来的数据都是规范的。保证 Controller 的出和入的规范就好了
    crysislinux
        2
    crysislinux  
       2022-06-17 21:47:17 +08:00 via Android   ❤️ 1
    一般来说我倾向于不依赖别的服务能同步验证的,就放在 entity 里,比如 email 格式。其他的就搞个 factory ,在创建 entity 的时候验证,比如保证 email 唯一
    leoskey
        3
    leoskey  
       2022-06-18 14:04:20 +08:00   ❤️ 1
    Abp 推荐的是 Email 校验在 EmailEntity 里完成对自身的校验。
    如果 User.Email 是 string ,那么就在 User.SetEmail 里完成。(ps: 添加 SetEmail 方法 或 属性的 set 访问器都可以)。
    email 唯一校验已超出 Entity 职责范围,应使用领域服务 UserManager 完成: userManager.SetEmailAsync(user, email)
    DreamStar
        4
    DreamStar  
       2023-03-01 11:40:54 +08:00
    非空, 非负, 长度啥的在应用服务就搞定.
    email 之类的实体数据, 用值对象解决, 构造的时候就判断了, 不可能有非法的.
    唯一类验证交给 repo 服务做 exist 判断, 并发创建唯一交给数据库唯一索引就行
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   921 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 21:49 · PVG 05:49 · LAX 13:49 · JFK 16:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.