关于领域驱动设计中的“合法”性校验?

各位在进行领域驱动设计开发的时候,实体的“合法”性是如何实现的,怎么保证实体的一致合法性?检验代码分布在实体中,还是在服务中,还是都有?如果分布在实体中,如何保证一次性校验,使用构造函数或工厂保证?如何得到校验上下文?有种说法是实体永远有效;另一种则相反,必须经过业务逻辑校验才能算是“合法”实体。大家怎么看?
相关讨论:http://lostechies.com/derickbailey/2009/02/15/proactive-vs-reactive-validation-don-t-we-need-both/

http://lostechies.com/jimmybogard/2009/02/15/validation-in-a-ddd-world/


[该贴被gsft于2013-05-01 10:29修改过]

之前是放在服务中,现在是用AOP拦截上下文的Submit事件,未来计划将校验信息存储到数据库中,可通过图像界面配置,并且可以在运行时更改需要拦截的事件,然后执行校验。

J2EE有针对此问题的规范JSR303,建议在实体中进行合法性的校验。为什么这样做呢?官方给出的理由是:既然在展现层、逻辑层、持久层都需要进行重复的校验,为什么不集中在实体上进行统一的校验,省时省力,多好呀!

显然,这个理由有一定的说服力。但其前提是什么呢?前提是将展现层的事情,在服务端完成;数据的约束,放在持久层完成,也是在服务端完成。

我个人不这么认为,
第一个理由:展现层的约束应该交给应用端,而持久层的约束应该交给数据库(数据表字段的约束设计)。服务端关注业务逻辑上的校验,至于展现约束与存储约束,不是该他做的事情;这样才有利于使业务模型更干净和清晰。(服务端只关注数据与逻辑的处理)
第二个理由:对与错,好与坏,脱离语境(上下文)是不能冒然下定论的,直接将验证逻辑绑定在实体上,无异于直接给一个人贴上好人或坏人,庸才或天才的标签。

你所引用的文章的主要观点,与我的第二个理由相同,支持在业务场景中进行合法性的校验
1)Think about that question, “is this entity valid”. You can’t answer that question unless you know the context for validation.
2)And that’s where I think we should move towards in terms of validation. Instead of answering the question, “is this object valid”, try and answer the question, “Can this operation be performed?”.

此外,合法性的校验是异常设计的一个重要部分,无法回避,甚至需要花不少的精力进行精心的设计。试以最为常见的空指针异常为例,空指针异常是一种运行时异常(unchecked exception),在服务端的校验中时常需要将其包装为编译时异常(checked exception),并在业务场景进行捕获,以响应状态码为400的响应返回应用端;而应用端的空指针异常,则反之,时常仍包装为运行时异常,在最靠近用户的地方捕获该异常,并以友好的提示信息呈现给用户。

当然,如果项目已经采用了服务端的展现框架,比如GWT、JSF、ZK之类的,开发者迫于进度的压力,管它什么干净、清晰的业务模型,按时交工,才是重之重,或许在实体上绑定校验逻辑,是上上之策,这是事实,也很难回避。

非常感谢大家的回复,这个帖子是我第一次在这边发帖;
持久层校验相对单纯;主要是业务逻辑上的校验可能会棘手点,特别是想把业务逻辑的校验封装在实体内部,很多时候会有不一致的情况发生;比如很多个setter必须在一个原子操作中才能满足业务校验规则,如果校验发证在实体中,这个时候又要考虑构造函数、Factory、Builder等等保证一次性创建一个满足业务逻辑要求的实体出来,稍微复杂了些;如果不放在实体中,实体又暴露了setter方法,较难控制其它程序直接调用会造成隐患。

一辆汽车在安装了2个轮子的时候,他就不能叫汽车,只有经过出厂前检测合格了,才是一辆完整的汽车。同样的道理,实体在提交或保存之前都不能叫做实体,所以无需校验。至于何时检验,这个动作应该是被动的,当触发了提交或保存事件时触发。

2013-05-02 07:31 "@gameboyLV
"的内容
一辆汽车在安装了2个轮子的时候,他就不能叫汽车,只有经过出厂前检测合格了,才是一辆完整的汽车 ...

说得很好,我认为这实际就是逻辑一致性的问题。实现上通过规格约束Specification来指定实体创造必须符合某种规格要求。这样,实体和规格类等附件组合成了一个聚合。符合聚合保证逻辑一致性的设计原则。

另外我想对DRY(不要重复你自己)原则说几句,这个原则应该置于逻辑一致性之下,只是一个战术性原则,经常因为自己的眼界和角度问题,使用DRY原则形成新的紧耦合,一只青蛙每天在井底下看到一片天,它就把天抽象为一个类,那个类的边界大小只有井口那么大,如果它跳出井,就发现原来为了不重复而抽象出类的细腻度和边界都是有问题的。

相关主题:
迪米特法则(Law of Demeter)与领域模型行为:
http://www.jdon.com/45378
[该贴被banq于2013-05-05 13:30修改过]