对领域驱动设计的初步认识(六)

10-11-17 flyzb
    第一次看到banq关于JF的PPT时,我突然发现原来可以这样建模,领域模型是这样丰富,让领域对象充满了色彩。过去看到的OO都是“简单实体建模”:即从需求中挖出几个实体对象,然后填充属性,至于其它的东西只要会查询就行了。这种建模方式随处可见,实在是太流行了,似乎让我们相信其实OO就是“贫血对象”。DDD之所以让领域对象富有生命,是因为值对象的存在。DDD采取的是“特征建模”思想,领域对象不仅仅有实体对象,还有表示状态的值对象。

    DDD强调“充血模型”。在复杂业务中,这很有用,可以提高对象的可复用性。但是要注意,“充血模型”虽然很好,但其背后却隐藏玄机。复杂场景中,如果所有的行为都放到了对象中,那领域对象就会变得沉重无比,带来各种副作用。同时,描述状态的值对象往往不是一蹴而就,而是充满着变化,难以把握。当业务不明时,"贫血模型"容易把握,这也就是为什么现在”充血模型“不多,而"贫血模型"大行其道的原因。

    DDD认为"领域对象应该是有行为的",大家都认可。如果说"贫血模型"造成了对象和行为的分离的话;那么"充血模型"也是有问题的,因为DDD中简单地认为把行为放到对象中就行了,殊不知这其实是关于对象和行为之间的一种静态地僵化的看法。而DCI对这种思想进行了修正,认为对象在场景中才具有行为。对于DCI中的对象(数据),我的理解和banq不同。我认为DCI中的对象(数据)是一种裸对象,但却不是简单意义上的"贫血对象"。DCI中的对象和行为是一种动态关系,是依赖于场景而存在的。这也就是说对象不会无缘无故的产生行为,(呵呵,那是神经病),对象具有行为一定是有意义的。也许有人担心场景会变成"贫血对象"模式下service那样的噩梦,造成对象和行为的分离。其实不用担心,在DCI中可以用"事件"把对象和行为联系在一起就避免了"贫血对象"的问题。呵呵,这是不是应了中国的一句老话:"分久必合,合久必分"。

    关于jivejdon中的Account,在我看来不太准确。我是从复杂场景来看这个问题的,我们都知道在企业里account这个领域对象是属于HR模块的,真正的Account对象并不独属于Jive这个域。所以在jivejdon中应该有一个独立的account对象,然后有一个AccountInJive对象,这个AccountInJive对象才拥有Account和AccountMessageVO值对象。(大家可以讨论一下:如果Account在另外一个服务器上,那么这个account在JiveJdon是贫血对象还是领域对象?)

    另外,对于DDD中的聚合,我也有不同的理解。其实DDD提出聚合的概念是为了保证领域内对象之间的一致性问题。但是我对DDD中对"聚合"这个概念的落地形式表示质疑,DDD特别强调聚合根的封装性,然而这可能会导致领域内对象之间的逻辑强耦合。也许有人说领域内部的对象是高内聚的,这样做没关系。但是在实战中,领域模型内部的值对象往往存在着变数,这是我们认识客观世界的必然规律。然而这会导致领域模型的不稳定性。所以我认为及时领域内部的对象也应该注意低耦合,这个问题同样需要靠事件来解决,事件才是保证领域对象一致性的关键。

[该贴被flyzb于2010-11-17 22:56修改过]

                   

8
banq
2010-11-18 10:12
写得很好,前面我基本赞同,我感觉如果你能再看一下“如何从职责和协作中发现丰富对象?”,其中主要是讲相互作用,事件的,会对你的思路有很大补充。

2010年11月17日 22:48 "flyzb"的内容
然而这会导致领域模型的不稳定性。所以我认为及时领域内部的对象也应该注意低耦合,这个问题同样需要靠事件来解决,事件才是保证领域对象一致性的关键。 ...

领域聚合内部的松耦合因为天然的业务的高聚合新,就只能通过软件设计上的松耦合方式来实现,比如设计模式等。

为保证一致性,一般采取在实体根对象中放入对值对象或其他状态实体修改的方法行为,外界不可以绕过根实体直接修改状态。

另外,值对象我过去也认为可适合保存状态,当时也有一些争论,现在看来不是很合适,状态应该属于实体的一个重要部分,如果状态复杂,可以用专门的状态模式等设计方式消肿。

所以,对于大家担心“充血模型”会变得沉重无比是多余的,因为这里面忽视了软件设计模式的作用。

总体设计来讲:分两个方面:根据业务的设计;根据纯软件设计目标松耦合的设计,通过这两种切分方式,可以将业务真正落实为好的软件,而我们过去常常注重业务的切分设计,忽视软件层面设计,也就是设计模式。

当然,掌握设计模式的人去进行业务分析设计时,发现设计模式毫无用处,这是因为他没有掌握业务建模设计的原因,业务建模设计好了,自然软件设计就派上用上。

业务建模设计和软件设计相当于切菜的大刀和小刀,对于整只猪,必须先用大刀,然后再用小刀。

有了这两把刀,就不用担心胖对象的沉重,相反,如果你一开始做成很瘦的只有数据的贫血模型,设计模式真的一无用处了,然后,你的系统就走上了依赖数据库数据表的系统,根本没有设计,用所谓OO语言,做delphi的活,白折腾,白牺牲脑细胞。

SpeedVan
2010-11-18 15:03
呵呵,和我想的差不多,但DDD也有存在价值的,就是对于一些“相对稳定”的领域,仍然具有快速开发的优势存在。例如一些不会考虑扩展业务的领域。“一般稳定”的目前是EDA和DCI具有优势。对于一些“绝对不稳定”的(简单桌面软件之类,关注面不在用户,只在功能上,时不时增加功能的那种,类似监控程序等),果断面向功能开发。

我觉得jivejdon是DDD上再作EDA的优化,框架混合使用本来就是很自然的事情╮(╯▽╰)╭

若要在实际中选择合理的架构,则需要架构师的拥有相当实力了。

而DDD中的聚合问题,我觉得更多的是生命周期一致性的问题。至于值对象,看它是统计什么,例如是统计message的,则看message聚合到哪里,是forum,则把该值对象聚合到forum,从这看来也是生命周期问题。想优化性能则引入异步事件。其实DDD是把以前忽略掉的生命周期问题,重新放上来,而且当作重点,也是为什么DDD领域中充满“生命”的原因。还有生命周期一致不代表需要同步。只是同生共死而已,不是你走一步,我走一步。所以存在变数是没什么问题的。

[该贴被SpeedVan于2010-11-18 15:05修改过]

[该贴被SpeedVan于2010-11-18 15:10修改过]

[该贴被SpeedVan于2010-11-18 15:11修改过]

flyzb
2010-11-18 21:49
    嗯。。banq说的我都明白,DDD的道路是正确的,这个没有问题。我只是想反思一下:为什么“贫血模型”大行其道,而DDD成功的案例少,失败的多呢?难道仅仅是懂DDD的人太少了吗?为什么有道友说“稳定的业务才适合DDD”呢?在这背后还有什么是我们忽略的东西呢?

业务建模设计和软件设计相当于切菜的大刀和小刀,对于整只猪,必须先用大刀,然后再用小刀。

    看来banq也同意DDD需要对业务精雕细琢才可以,可是浮躁而且不了解企业业务的软件公司对业务分析常常会发生偏差,甚至是大的错误,这会导致业务模型在后期发生较大的变化,而原来的基于错误业务的设计模式反而成了掣肘。对于DDD而言,很可能是“领域特征”——值对象发生了剧变,那么有关值对象的一切逻辑都要变化。而DDD采取的以下做法会造成领域内部的业务逻辑难以重构。

为保证一致性,一般采取在实体根对象中放入对值对象或其他状态实体修改的方法行为,外界不可以绕过根实体直接修改状态

    “一致性”究竟是什么呢,我认为“一致性”是事物相互作用的本质内在联系,也就是在一定场景下外界刺激在沿着一定路径传递而导致一系列对象的变化。所以“外界不可以绕过根实体直接修改状态”并不能反应这一本质,因为外界刺激并不全都是先作用在根对象上面的。在我看来,这种非本质的封装反而会造成耦合,尤其是采用“直接调用”的形式。应该说,“直接调用”是造成对象耦合最大根源,因为“直接调用”是在强调对象的上下级关系,这很生硬。如果我们换一种方式,用一种平等的心态去看待对象间作用关系,用“告诉我做什么”的方式而让对象间解耦。

    其实这里还有一个关于“边界”的问题,如果“外界不可以绕过根实体直接修改状态”,那么就会出现2个边界:Service和根对象。显然不应该出现2个边界,我认为边界只有一个,那就是service。在service(边界)内部,对象的关系应该尽量的平等,“对象间用消息来相互沟通”。我认为采取这种设计方式,既能保证对象间的一致性,又能保证领域对象的可扩展性。

    呵呵。。这点分歧可能有点大,欢迎大家讨论。

[该贴被flyzb于2010-11-18 22:14修改过]

jdon007
2010-11-18 23:30
2010年11月17日 22:48 "flyzb"的内容
呵呵,这是不是应了中国的一句老话:"分久必合,合久必分"。 ...

在思考语言范式时,我曾这样想过,面向对象,行为与属性绑得太紧,面向过程,行为与属性放得的太松。但这里不是仅仅选择“分”或“合”那么简单,“贫血模型”与“充血模型”实际上与“面向过程”与“面向类(对象)”的矛盾是相似的。

“贫血对象”是将“行为”与“属性”完全放开的一种表达,而“充血对象”则又矫枉过正,把“行为与属性”绑得太紧。

类是表达共性的概念,而对象则是充满个性,而且这些个性是依赖场景的,离开场景将失去意义。所以,在“充血模型”中,用类表达对象时,实际是将“个性”统统视为“共性”,在任何具体的场景中,对象的角色或职责都已经定义好了,这显然是不合适,因为一个对象可以多种角色参与不同的活动或场景(可能使得类继承体系非常庞大和复杂),而且在参与新的活动或者场景时,以“类”及“继承”的方式定义对象则更是力不从心。

而在“贫血模型”中,则将“共性”统统视为“个性”,这是抹掉“共性”的做法,与“充血模型”抹掉“个性”的做法刚好相反。前者是“白马非马”,后者则是“白马即马”。都没有协调好“共性”与“个性”的关系。

因此真正自然的“领域模型”应该是这样的,如果对象的某些行为在任何场景都是通用的,那么就放在领域中去,将其绑定,这是尊重“共性”的约束;如果对象的某些依赖于具体的场景,那么则在具体的场景中注入相应的行为,赋予对象相应的角色,这是尊重“个性”的自由。

那么“贫血模型”与“充血模型”,就没有用了吗?也许是。但这两个概念还有意义的:“贫血模型”与“充血模型”实际表达的是两个极端的情景。如果一个对象,没有任何共性的行为,其行为完全依赖于场景,则可用“贫血模型”表达;如果一对象,在所有的场景,都是以同样的角色身份参与,那么可用“充血模型”表达。

“贫血模型”与“充血模型”都可以实现领域建模,与“面向过程”与“面向对象”都可以描述一切画面一样。只是针对的问题不同时,有合适与不合适的差别。

所以,对象的行为该不该放入“领域模型”,我们要先分析一下这些行为是对象所固有的,还是依赖于场景的,如果是固有的,即是共性的,就放入领域模型(domain),如果不是则延迟在具体的场景(service)中注入,赋予其角色的个性(DCI)。

那么设计模式将如何运用呢?

设计模式可以在领域模型中使用(domain),也可以在具体业务场景(service)中使用。设计模式是在局部、微观层面的一种支持变化的机制,在具体业务场景中使用再合适不过了。将来可能会出现的现象是,在领域层(domain)各个模型中用的更多是“结构型”模式,而在业务层或服务层(service)的各个场景中用得更多的是“行为型”模式,两者都可以使用“创建型”模式。

此外,我认为DCI与MVC是互补(对称)而不取代的关系,这点与banq不同。有时间,我将描述自己如何从不同的思路得出这个结论,尽量浅显易懂,让更多的人理解,Jdon有时的讨论,让人不知所云,也许有话题较深入的原因,但也不能不自我反省。此外,我上面的分析,没有道出领域建模的真正意义和OO最初的构想(梦想),以后有时间一块补上。

猜你喜欢
4Go 1 2 3 4 下一页