关于领域驱动设计(DDD)的理论知识
摘自:http://www.msaspx.com/?soft/thread-1890-1-4
最近一直在学习领域驱动设计(DDD)的理论知识,从网上搜集了一些个人认为比较有价值的东西,贴出来和大家分享一下:
我一直觉得不要盲目相信权威,比如不能一谈起领域驱动设计,就一定认为国外的那个EricEvans写的那本书中的一些概念就一定是正确的,认为领域驱动设计就一定是聚合,聚合根,实体,值对象等概念。我们要有自己的思想,要有自己判断真正的领域模型该是什么样子的勇气和追求。
1."领域驱动设计"=“问题域模型驱动领域建模”+“领域建模驱动软件实现”
2.问题域建模的过程就是业务领域分析的过程,对于企业而言就是业务架构的分析和建立过程,这里不包含任何OO的设计成分,主要从组织、流程和业务能力三个维度来分析业务。
3.记住很多模式没有什么用处,带着问题在模式中寻找答案才是正确的使用方式,让那种解决方案的思想融入到你的模型当中,然后彻底地忘掉那些所谓的模式名词。
4.好的领域建模应该具有柔性,能够伴随着用户一起成长。
5.这让我意识到业务建模应该回归自然:一谈起来建模技术,就离不开国外提出的OO、EDA之类的东西,其实我们的老祖宗早就有了“摸脉”的说法,现在的SOA、ESB之类的东西是不是就像打造一个企业的“神经脉络”,而“OO”是不是就像“神经元”,它们之间的通讯就是靠生物电脉冲,这就是消息驱动。
6.《领域驱动设计》一书中只是强调了业务的水平分割,然而在大项目里还有垂直分割,注意垂直分割不完全等同于包的划分。目前有一种非常错误的做法,就是一上来就开始对象建模,然后再进行归类划分模块;正确的做法应该是前期以确认领域边界功能为主,后期以确认领域内的对象模型为主。关于领域的切分,《领域驱动设计》没有过多谈及,其实方法就是不断对企业业务知识的学习和分析。当你对一个业务认识不清的时候,最好的办法就是不同企业环境下去分析这个业务,那这个业务的所有发展变化就清楚了,这就像那些生物学家总是喜欢通过长期的野外考查来学习知识。这个工作做好了,项目就成功了50%。
7.领域的边界就是服务,也是对外提供服务的唯一入口。领域服务和领域对象模型是一个业务领域的2个不同侧面。领域服务强调是从外向内看,反映了“外部对业务领域的使用功能”;领域对象模型强调业务领域就像一个独立的具有一定自主能力的生命体,反映了“业务领域的内部运行机制”。领域对象模型的功能是不能对外暴露的,不然会造成外部对领域对象的耦合。
8.不要一说“面向关系设计”就是错的。因为用户的角度看,业务本来就是面向关系和过程的,这非常自然;而从设计看,业务是不同主体在相互作用。这就是为什么越靠近用户的地方面向关系和过程的设计味道越浓的原因了。
9.“类和职责”的叫法让我总感觉比较僵化,像是没有生命的死细胞一样,觉得这是一种西医模式。我更崇尚一种中医模式,强调建模是动态的、基于场景交互的,应该用更自然的还原业务本来面目的眼光去审视建模过程,也就是说“有机的业务建模”实际上就是“技术建模”的问题域建模,而“技术建模”只是“业务建模”的技术落地而已。
10.关于建模工具,像“用例图,流程图,状态图之类”的并不是我理想的建模工具,虽然他们确实能表达一些东西。我理想的业务建模工具应该是能从角色(组织或者人)、流程、业务能力三个维度立体地、动态地分析描述业务模型,希望可以是一个动态的3D视图和流图,并可以按不同的维度分析展开。
11.那对于我们做企业业务建模,终极目标应该是个什么样子呢?我认为这个终极目标一定是非常复杂的(也就是我原来常说的大项目场景),因为只有在复杂的场景下才能真正检验各种建模技术的偏颇。想想看,我们的建模目标应该向谁学习。论坛也有人说过,“自然的就是最好的”。是啊,经历过亿万年才进化出来的模型难道不值得我们学习吗,难道不是我们的目标模型吗!呵呵,答案已经呼之欲出了,就是“仿生物建模”。是的,没错,如果说利用我们的建模技术能够去构建出一个复杂的仿真人,具备人的一些特征和功能的话,那这种建模技术就是完美的。
12.企业的业务建模可以分为两个层面,”宏观建模”和“微观建模”。“宏观建模”是指首先要对企业做一个整体的信息化规划,对企业进行整体的的业务架构建模,其成果就是业务组件。其中的方法论可以参照IBM的CBM,不过IBM好像也只是咨询,真正的落地还要靠自己对CBM的领悟。Evans的DDD主要属于“微观建模”部分。对于“微观建模”,我认为分为2个方面:“结构化建模”和“行为建模”,这是一体两面。我觉得Evans对DDD总结了几个关键的要素:实体、值对象、聚合、工厂和存储,但其中还少了一个非常重要和关键的要素:“事件”。
13.众所周知,人体是由很多细胞构成的,那细胞之间是如何作用的呢,其实就是“刺激”和“响应”。其中“刺激”就是“事件”,所以“事件”是业务模型本来就应该具备的要素,而不是什么技术层面的东西。从“事件”角度看,“职责的本质就是事件的响应”。
14.“结构化建模”是指建模中除了静态的实体和值对象的结构关系外,从“事件”角度看,实体或者值对象还具备一些“本能的反应”,比如"手指会弯曲"。而“行为建模”是指通过神经中枢(消息总线)来控制不同对象的本能反应来完成一个复杂的组合,比如"用手弹钢琴"。
15.“一上来先识别类,然后就考虑怎么分配职责”的做法是一种本末倒置的僵化的静态建模。这种方式往往让人落入一种只注重“形”而忽略“意”(业务本来的面目)的怪圈。我认为建模分为2个阶段,第一个是“有机的业务建模”,第二个是“技术建模”。“业务建模”和“技术建模”的区别是:“业务建模”完全是业务语言,不含任何技术成分,比如“类、值对象和职责”的概念,在“技术建模”中才涉及那些概念。“业务建模”做好了,“技术建模”就成了自然而然的东西了。
“有机的业务建模”的一个非常重要的特征就是“有机”,就是强调业务模型中的每一个“业务主体”都是一个生命体。这些业务主体都有“生命特征”,在一定条件下受到外界的刺激就会发生一定的行为。这些业务主体构成了整个业务模型。当然,这些业务主体的规模和层次不同:比较大的业务主体就是“领域主体”,他有明确的领域边界;而领域主体的对外界响应其实是通过内部的“业务核心主体”的协作来完成的,有的看见的见摸得着————具有业务实体的特点,有的比较抽象————控制规则和流程,当然有的“业务核心主体”可能是个多核细胞。“有机的业务建模”有一个非常重要的工作就是“检查业务模型的生命健康状况”。需要注意的是这个业务建模可能是通过不完善的用户需求建立起来的,所以我们需要构建多种场景通过不同的刺激去看这个领域主体内部的响应中是否有“异常情况”,如果有那就要开刀了。
关于“技术建模”,我认为“对内功能和对外功能要分开”,领域对象不建议同时承担对内和对外的功能,但小项目除外。服务对外要封装和隐藏领域内部的东西,提供的是服务接口;同时服务要负责领域内部对象的行为组装。从另外一个层面看,服务是需求,领域对象模型是实现。值对象的本质就是反映业务主体的不同业务特征,可能是业务主体的临时状态(比如用户的发帖数)或者属于另一业务主体的状态值。从微观看,聚合是交互最紧密的业务对象的封装,从宏观看,聚合是一个具有明确业务边界的独立业务核心主体。所以聚合只是形式,业务模型的正确建立才是决定要素。
16.相互作用原则全面、深刻地揭示了事物之间的因果联系,是因果关系在逻辑上的充分展开。在客观世界的普遍联系链条中,原因和结果经常互移其位、相互转化。受原因作用的事物在发生变化的同时也反作用于原因,从而把因果性关系转变为相互作用的关系。其中每一方都作为另一方的原因并同时又作为对立面的反作用的结果表现出来。整个物质世界就是各种物质存在普遍相互作用的统一整体,相互作用是事物的真正的终极原因,在它之外没有也不可能有使它运动和发展的原因。相互作用也是系统内部诸要素的关系和联系的形式。要素之间相互作用的方式构成系统存在的基础,系统中要素的相互作用是决定系统发展方向的因素。相互作用只有借助于特殊的物质载体才能实现,相互作用的内容取决于组成要素的物质层次和性质。例如,现代生物学把相互作用划分为分子的、细胞的、器官的、机体的、种的、生物圈等不同水平的形式。社会生活是最复杂的相互作用的形式。相互作用是客观的、普遍的。具体的相互作用是整个物质世界相互作用链条的环节和部分,相互作用的普遍性和绝对性通过无限多样的具体的相互作用而体现出来。相互作用是事物的属性、结构、规律存在和发展的条件。相互作用范畴具有重要的方法论意义。认识事物意味着认识它们的相互作用,要揭示事物的本质属性,就必须研究事物之间具体的相互作用的特殊性。相互作用的实质是矛盾以及矛盾诸方面的相互依存和斗争。在诸多因素的相互作用中,必有一种起着主导的决定的作用。在实际工作中,只有认清事物之间相互作用的特点和规律性,才能认识和把握事物的本质。“事件”这个词的确有技术范畴的嫌疑,但说到“相互作用”、“输入和输出”,大家都会明白的。过去的OO过于僵化、教条,偏重于静态场景,忽略了客观世界运动和相互作用的本质规律,而最近出现新技术和对OO的反思“正在回归自然”。
17.“世界是有事物及其活动组成”或者“世界由事物及其相互作用组成”是非常朴素、直观的世界观。
18.建议学习“经过亿万年进化而来的客观存在的生物控制模型”,我们都知道“人体是通过神经网络来控制不同的肌肉、骨骼来做出各种复杂的动作的”。“刺激和响应”是客观存在的,而“场景”只是我们对客观世界的主观认识。“刺激和响应”,举个例子,人的最外层的皮肤就如同“门面”,用来接受外界的“刺激”,内部再由“大脑”下达指令,指挥各个“肢体”进行“响应”。“神经网络”是被这层皮肤覆盖加以保护。“神经元”向“皮肤”注册可能完成的“响应”,而“皮肤”接受“刺激”后由大脑进行反馈而回调“神经元”,“神经元”再完成各个指令动作。
19.业务建模应该遵守客观规律。
20.DDD强调“充血模型”。在复杂业务中,这很有用,可以提高对象的可复用性。但是要注意,“充血模型”虽然很好,但其背后却隐藏玄机。复杂场景中,如果所有的行为都放到了对象中,那领域对象就会变得沉重无比,带来各种副作用。同时,描述状态的值对象往往不是一蹴而就,而是充满着变化,难以把握。当业务不明时,"贫血模型"容易把握,这也就是为什么现在”充血模型“不多,而"贫血模型"大行其道的原因。
21.DDD认为"领域对象应该是有行为的",大家都认可。如果说"贫血模型"造成了对象和行为的分离的话;那么"充血模型"也是有问题的,因为DDD中简单地认为把行为放到对象中就行了,殊不知这其实是关于对象和行为之间的一种静态地僵化的看法。而DCI对这种思想进行了修正,认为对象在场景中才具有行为。对于DCI中的对象(数据),我认为DCI中的对象(数据)是一种裸对象,但却不是简单意义上的"贫血对象"。DCI中的对象和行为是一种动态关系,是依赖于场景而存在的。这也就是说对象不会无缘无故的产生行为,(呵呵,那是神经病),对象具有行为一定是有意义的。也许有人担心场景会变成"贫血对象"模式下service那样的噩梦,造成对象和行为的分离。其实不用担心,在DCI中可以用"事件"把对象和行为联系在一起就避免了"贫血对象"的问题。呵呵,这是不是应了中国的一句老话:"分久必合,合久必分"。
22.对于DDD中的聚合,我也有不同的理解。其实DDD提出聚合的概念是为了保证领域内对象之间的一致性问题。但是我对DDD中对"聚合"这个概念的落地形式表示质疑,DDD特别强调聚合根的封装性,然而这可能会导致领域内对象之间的逻辑强耦合。也许有人说领域内部的对象是高内聚的,这样做没关系。但是在实战中,领域模型内部的值对象往往存在着变数,这是我们认识客观世界的必然规律。然而这会导致领域模型的不稳定性。所以我认为及时领域内部的对象也应该注意低耦合,这个问题同样需要靠事件来解决,事件才是保证领域对象一致性的关键。
23.“一致性”究竟是什么呢,我认为“一致性”是事物相互作用的本质内在联系,也就是在一定场景下外界刺激在沿着一定路径传递而导致一系列对象的变化。所以“外界不可以绕过根实体直接修改状态”并不能反应这一本质,因为外界刺激并不全都是先作用在根对象上面的。在我看来,这种非本质的封装反而会造成耦合,尤其是采用“直接调用”的形式。应该说,“直接调用”是造成对象耦合最大根源,因为“直接调用”是在强调对象的上下级关系,这很生硬。如果我们换一种方式,用一种平等的心态去看待对象间作用关系,用“告诉我做什么”的方式而让对象间解耦。
24.在思考语言范式时,我曾这样想过,面向对象,行为与属性绑得太紧,面向过程,行为与属性放得的太松。但这里不是仅仅选择“分”或“合”那么简单,“贫血模型”与“充血模型”实际上与“面向过程”与“面向类(对象)”的矛盾是相似的。
“贫血对象”是将“行为”与“属性”完全放开的一种表达,而“充血对象”则又矫枉过正,把“行为与属性”绑得太紧。
类是表达共性的概念,而对象则是充满个性,而且这些个性是依赖场景的,离开场景将失去意义。所以,在“充血模型”中,用类表达对象时,实际是将“个性”统统视为“共性”,在任何具体的场景中,对象的角色或职责都已经定义好了,这显然是不合适,因为一个对象可以多种角色参与不同的活动或场景(可能使得类继承体系非常庞大和复杂),而且在参与新的活动或者场景时,以“类”及“继承”的方式定义对象则更是力不从心。
而在“贫血模型”中,则将“共性”统统视为“个性”,这是抹掉“共性”的做法,与“充血模型”抹掉“个性”的做法刚好相反。前者是“白马非马”,后者则是“白马即马”。都没有协调好“共性”与“个性”的关系。
因此真正自然的“领域模型”应该是这样的,如果对象的某些行为在任何场景都是通用的,那么就放在领域中去,将其绑定,这是尊重“共性”的约束;如果对象的某些依赖于具体的场景,那么则在具体的场景中注入相应的行为,赋予对象相应的角色,这是尊重“个性”的自由。所以,对象的行为该不该放入“领域模型”,我们要先分析一下这些行为是对象所固有的,还是依赖于场景的,如果是固有的,即是共性的,就放入领域模型(domain),如果不是则延迟在具体的场景(service)中注入,赋予其角色的个性(DCI)。
25.设计模式可以在领域模型中使用(domain),也可以在具体业务场景(service)中使用。设计模式是在局部、微观层面的一种支持变化的机制,在具体业务场景中使用再合适不过了。将来可能会出现的现象是,在领域层(domain)各个模型中用的更多是“结构型”模式,而在业务层或服务层(service)的各个场景中用得更多的是“行为型”模式,两者都可以使用“创建型”模式。
26.不要把业务和领域等同,业务是客观存在,领域是主观认识。
27.领域和场景是不同的。领域是宏观层面的,场景是微观层面的,虽然它们有相似点。
如果可以用一个领域模型来描述“企业”的所有业务,我当然希望是这样的,但问题是有谁可以做到,那就是共产主义了,完全可以凭此打败微软、IBM之类的公司成为老大了。领域“有界无边”,说起来拗口,简单说领域虽然有界,但其边界时可以扩展的,领域虽然有界,但其边界是无形的。比如说音乐界不断发展,但界还是存在,虽然界也是一种主观认识的划分。因为音乐界不能独立于人类的所有活动。
28.领域的边界是由用户的需求来决定的,用户的需求是动态可扩展,所以领域的边界也如此。所谓领域语言,就是从有所需的用户心中提炼出来的。用户需求相对于领域来说是一个“客观存在”。
29.领域虽然是对客观存在主观上一种划分,但不是一成不变的。一个领域模型是根据业务(用户需求)对领域的一种素描,业务不同,素描也就不同。
30.我们应该学会逆向思考:
1.服务是什么?自己在过去的项目中用过服务吗?如果不用,会怎么样?如果用了,用好了吗,出现了DDD指出的问题吗?
2.聚合是什么?为什么要用聚合?自己用过聚合吗?DDD的聚合采取的形式是什么?除了DDD中的聚合,还有别的聚合吗?
对DDD其他的要素可以以此类推。其实DDD不太适合初学者和小项目,DDD是经历过很多项目的失败和成功才总结出来的。所以我是反对把DDD当成一种理论学习的,在实战中不懂得东西不要乱用,但是一定要感悟,也就是说你可以先不去用DDD,但可以在实践中感悟DDD的思想和要素,然后明悟真伪。所以我发了这么多帖子,希望大家从自己的实战去印证,共鸣。这里不是没事无聊的闲谈,而是基于实践的关于DDD的灵魂的交流。
31.DDD是对一些复杂IT项目成功要素的总结而形成的一种理论。正确地理解DDD是关键,只要成功运用DDD的思想和要素就可以了,绝不是照搬。就算没见过DDD,也可能在实践中运用过DDD的思想和要素。
32.领域建模与设计模式的思想不是只有高手才能领悟,就像数学与哲学思想不是只由数学家和哲学家才能领悟。思想与知识是两个相关又不同的范畴,建模思想与技术知识/经验同样如此。不要因为新手知识的不足,而认为新手就不能领悟思想。如果出现这种情况,我只有两种理解:a)这个老手不懂;b)这个老手懂,但不想告诉你,可能出于自私,也可能觉得你还不适合。
33.不同于设计模式的关注点在如何设计模型以支持需求的变化,分析模式的关注点如何将需求转化为模型。MartinFowler的《分析模式--可复用的对象模型》、PeterCoad的《对象模型--策略、模式、应用》等等关于分析模式的著作,都尚未拜读,加上经验实在匮乏,所以本不想说。可是对于PeterCoad的《JavaModelinginColorwithUML》一书中的四色模型,一见如故,极其简单的四色模型,在我心里已经将其作为自己关于分析模式的核心认知,这里说说自己对四色原型的理解(再次感谢Jdon让我遇见了四色模型)。
四色模型中有四个最核心的概念(刚好设计模式也是运用了OO的四个基本概念,无巧不成书呀),分别是MI(MomentInterval)、Role、PPT(PartyPlaceThing)、Desc(Description),不同设计模式运用的OO的Abstraction、Encapsulation、Inheritance、Polymorphism四个基本概念在中文有很好的翻译:抽象、封装、继承、多态。这四个词,直译起来,似乎没什么感觉:瞬间-间隔时间、角色、聚会场所-物件、描述。查阅一些资料,其描述我不是十分满意,这里我尝试用简短的言语解释其本质:MI描述活动,Role代表参与活动的事物;PPT代表未参与活动的事物,Desc描述事物。举个例子,比如一件商品,商品本身就是PPT,商品的类别信息、厂家等商品的元信息就是Desc,如果这件商品为顾客所购买,此件商品就变成了Role(当然顾客此时也是Role),而顾客购买商品这个活动就是MI。
世界可以简单理解为由事物及其运动(MI)构成,事物又有动静之分,动即参与活动之事物(Role),静即未参与活动之事物(PPT),此外,可能还需要描述事物的信息(Desc)。为了将原型形象化,我们可以根据色彩的冷暖动静之感,将活动(MI)涂上活跃的红色,将参与活动的事物(Role)涂以暖色黄色,将未参于活动的事物(PPT)涂以安静的绿色,将描述事物的信息(Desc)涂以冷色蓝色。这样四色原型就诞生了,通过四色原型,我们可以像孩子拼接积木一样,一块一块地拼接出需求模型的原型了。
34.分析模式将需求转化为原型,设计模式支持需求模型的变化,而实现模式为何而生?实现模式关注的是代码本身,我们在分析和设计上付出的努力,终究要落实在代码上。分析和设计再好的模型,也可能被充满smell的代码掩盖或破坏。KentBeck在《实现模式》一书提出了77个实现模式,6条原则和3种价值观。77个实现模式涉及一些具体的代码规范的建议,暂且不论;6条原则,局部化影响、最小化重复、将逻辑与数据捆绑、对称性、声明式表达和变化率,也暂且不议;3种价值观:沟通、简单、灵活,我也只想说说其中的一点:沟通,因为个人认为这是实现模式最根本的意义所在。KentBeck在书中说:过了20年,把别人当作空气一样的编程方式才在我眼中褪尽了颜色。作为一位programmer,代码的作用不仅在于与机器交流,更重要的是与别的programmer交流,将“沟通”的价值观贯彻到代码的编写中,是值得每个programmer坚持去做的事情。上面所说的三本很薄的书,可以作为关于分析、设计、实现模式的认知基础,值得收藏。