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

    首先声明,《领域驱动设计》对我来说只是一种建模技术,我讨论的前提是在面对企业多业务集成(如ERP、MES、HR、多项目、PDM、MRO)模式下的如何解决业务建模的问题。我提出的观点也许并不适合小项目甚至中型的单项目,因为技术架构和项目复杂度是相适应的。
    经过多年的企业实战,我对企业业务模型有了一定的认识,但关键是目前感觉还是技术滞后于业务的问题。我认为记住很多模式没有什么用处,带着问题在模式中寻找答案才是正确的使用方式,让那种解决方案的思想融入到你的模型当中,然后彻底地忘掉那些所谓的模式名词。
    上次我说“领域模型应该具有柔性”,这是我花了5年时间来打磨一个项目的过程中亲身感悟出来的。这让我意识到业务建模应该回归自然:一谈起来建模技术,就离不开国外提出的OO、EDA之类的东西,其实我们的老祖宗早就有了“摸脉”的说法,现在的SOA、ESB之类的东西是不是就像打造一个企业的“神经脉络”,而“OO”是不是就像“神经元”,它们之间的通讯就是靠生物电脉冲,这就是消息驱动。呵呵,当你在项目开发中感觉不自然的时候,那一定是你的模型出问题了,当然麻木除外。
    好了,回归正题。首先,谈一下“领域驱动建模”的入手问题。由于我针对的是大项目,所以我特别强调整体思考,进行自上而下的进行大的业务划分,先确定不同业务领域的边界。《领域驱动设计》一书中只是强调了业务的水平分割,然而在大项目里还有垂直分割,注意垂直分割不完全等同于包的划分。目前有一种非常错误的做法,就是一上来就开始对象建模,然后再进行归类划分模块;正确的做法应该是前期以确认领域边界功能为主,后期以确认领域内的对象模型为主。
    关于领域的切分,《领域驱动设计》没有过多谈及,其实方法就是不断对企业业务知识的学习和分析。当你对一个业务认识不清的时候,最好的办法就是不同企业环境下去分析这个业务,那这个业务的所有发展变化就清楚了,这就像那些生物学家总是喜欢通过长期的野外考查来学习知识。这个工作做好了,项目就成功了50%。
    领域的边界就是服务,也是对外提供服务的唯一入口(这点可能和banq不一致)。领域服务和领域对象模型是一个业务领域的2个不同侧面。领域服务强调是从外向内看,反映了“外部对业务领域的使用功能”;领域对象模型强调业务领域就像一个独立的具有一定自主能力的生命体,反映了“业务领域的内部运行机制”。
    领域对象模型的功能是不能对外暴露的,不然会造成外部对领域对象的耦合。我就亲身体验过域对象到处暴露而造成整个系统形成了一个领域对象相互关联的蜘蛛网,当项目小时无所谓,而一旦项目变大需要不同的部分独自发展而无法切割开来。另外一个原因是领域模型是不断变化的,比如“添加一个user对象”这个功能。一开始,就是业务很简单,认为放到User对象中就OK了;但是后来业务复杂了,“领导说了怎么随便创建用户呢,必须先审批”;最后业务更复杂了,“在不同的企业里这个审批的流程还不一样”。大家看一下,对于外部而言,如果一开始就调用的user域对象的方法就惨了,因为后来增加的业务应该是user与其他对象协作一起完成的,不能放在User内部,但是放在服务里就没问题了,外界只关心提供必要的创建user的信息就OK了,不关心内部怎实现的,怎么变化的,甚至于内部有没有User这个对象都没关系。注意服务里的addUser和User里的add方法是同时存在的,但侧重点不同。服务里的ADDUser是业务方法,而User的add方法仅仅是对象方法,前者调用后者。这就是封装,同时也支持了一定的柔性。这也是为什么在小项目里,有人觉得服务里写add,对象里写add没什么区别,甚至对象里写add更简单明了的原因了。
    什么样的对象适合作为聚合根的领域对象呢?一般而言,user在大多数情况下都不是领域对象,而customer可以是。因为user是泛指,除了在HR中user可以是领域对象。你可以想象一下,在一个企业里有好几个不同的单业务系统,都是基于领域驱动设计的;在这些不同的业务场景下,user都可以聚合根,这很正常,因为人可以干不同的事情。但如果把这些业务集成起来就有问题了。所以说作为聚合根的领域对象应该是业务场景类,而不应该是类似user的这样的类。当然,在小项目或者单项目是不存在这样的问题的。
    User作为另外一个域中的对象,在其他领域内如何引用呢?大家都知道,其实大多数业务应用中只需要一个仅仅包含UserId和UserName属性的对象就够了,显示足以。所以User在其他域里是值对象,但却不需要引用像rich domain中的那样的User.
    呵呵,这个帖子的观点也许有些不同,让讨论更猛烈些吧。
[该贴被flyzb于2010-10-19 09:03修改过]

哇塞。。太帅了。。呵呵。。

相当深刻啊。。

后面像道出了DCI的模型了,你可以去看看。至于文中水平分割,垂直分割···这么抽象?没听过这样的说法,数据库倒听过。事务有串行事务(同步,如流程),有并行事务(异步,如响应)。至于你所说的并行划分,是指寻找服务(找到业务就横着放着一起,关联时才联系起来)??

分层就分层,不分服务,只有分析设计领域时才,分析出服务。至于包划分,这些是人为的,根据状况设计的,目的是使项目更加清晰(你完全可以只有一个包,分析设计的逻辑仍然在里面的,但这样的项目不易读)。

呵呵,讨论越来越深入,不错。
下面说说我的理解:
我个人觉得如果仅仅按照<<领域驱动设计>>这本书上面讲的东西去领域建模会有点困难,为啥呢?首先一个复杂的业务系统,逻辑必然很复杂,那么逻辑放到哪里呢?领域模型实体对象?还是领域服务呢?领域驱动设计没有明确的告诉我们到底哪些逻辑放到实体对象里,哪些业务逻辑放到领域服务里,我倒是觉得结合四色原型的分析方法会更好,四色原型用一句话概括就是:一个具有某种描述(蓝色DES)的实体对象(绿色PPT),以某种角色(黄色)在某个时间段内(红色MI)对某个实体进行了某种操作(红色MI),最终产生了某种结果(绿色PPT),那么我目前的做法就是业务逻辑的操作是在角色里面,这种操作是角色所具有的(类似DCI中的场景),而具体如何做的是由红色MI模型去做的,而MI模型做的过程中又会和实体对象进行协作,这样就比较清晰一点.

PS:大家还有什么其它的想法,也分享下。

2010年10月19日 00:41 "flyzb"的内容
现在的SOA、ESB之类的东西是不是就像打造一个企业的“神经脉络”,而“OO”是不是就像“神经元”,它们之间的通讯就是靠生物电脉冲,这就是消息驱动 ...

非常厉害的比喻,这是只有宏观思维的人才会有这样形象描述。

楼主对服务和实体模型的区别描述也非常不错,为解决松耦合,DDD还提出专门的防腐层。

>这也是为什么在小项目里,有人觉得服务里写add,对象里写add没什么区别,甚至对象里写add更简单明了的原因了。
现在很多初学者会将业务逻辑写在服务中(他们甚至不会写两个add方法),User或Customer这样的实体模型都是贫血模型,只有setter/getter,没有其他业务方法如add等,这些都是典型的面向数据库编程思路。

[该贴被banq于2010-10-20 07:55修改过]

看了LZ的这三篇初步认识,收获颇大

2010年10月19日 00:41 "flyzb"的内容
现在的SOA、ESB之类的东西是不是就像打造一个企业的“神经脉络”,而“OO”是不是就像“神经元”,它们之间的通讯就是靠生物电脉冲,这就是消息驱动。 ...

这比喻有一点问题,“‘OO’是不是就像‘神经元’”,OO作为一种思维怎么却成为了SOA、ESB等架构和技术的组件?作为“神经元”,我觉得更应该是“类”、“技术”、“内嵌架构”等一些具体的内在的东西。而反过来OO应该对应着生物界或者经脉界的世界观(例如对一般人而言只知道神经,但没有神经元等认识,或者在其他世界观中,神经已不再是神经,例如不是OO的话,对象就不是对象)。嘛,只是对这比喻分析一下而已。

回正题,比较同意 xmuzyu ,在DDD中对设计依然很模糊,因为缺少针对业务的具体分配的准则,但业务的分配通常就是我们设计的一个难点,也是一直以来,最头痛的地方(看看贫血和充血模型争了多久)。其实以前banq已经说了,分析与设计,而我们学了很多设计,却缺少了一种科学的分析所支持。而分析很多人都自成一套,有些很不规范,有些很不科学,有些甚至不可取。而四色和DCI的确一套不错的分析与设计方案。

回过头来看DDD,本人感觉它更多的是带我们进入领域世界,让我们认清领域模型就是现实模型的直接映射,不应该被入侵和破坏。同时为了创造出一个纯领域环境,也给出了设计方案。

    在上一个帖子里,我强调了——"领域驱动设计" = “问题域模型驱动领域建模” + “领域建模驱动软件实现”。其中我认为“问题域模型驱动领域建模”比“领域建模驱动软件实现”更重要。可是从大家的回复看,强调后者的居多,也就是重视技术研究的居多,重视业务研究的少。说实话,IBM、微软之类的技术实力很强,但为什么没有一个通吃的企业管理软件,就是企业业务太复杂多变了。
    关于水平分割和垂直分割的问题,我想有过大项目和业务集成经验的人都能明白。水平分割就是分层,而垂直分割是按照不同领域做的业务组件分割(可以看看IBM的CBM的资料)。
    关于“神经脉络和神经元”只是一种比喻,我还没有想以此提升到理论的高度,只是想表达以一种“自然观”去审视各种建模技术。不要被各种技术名词所诱惑,需要掌握的是各种建模技术的问题领域,这才是“领域驱动设计”的真谛。
    我和banq不同。banq是布道者,希望通过各种技术的介绍让大家迈入领域设计的大门;我是实战者,各种技术只是用了就忘掉的工具,心中只有一个属于我自己的企业领域模型。
[该贴被flyzb于2010-10-20 15:24修改过]

2010年10月20日 15:22 "flyzb"的内容
我和banq不同。banq是布道者,希望通过各种技术的介绍让大家迈入领域设计的大门;我是实战者,各种技术只是用了就忘掉的工具,心中只有一个属于我自己的企业领域模型。 ...

你好,请问你在实战中采用什么样的建模技术手段来实现领域模型,其实领域驱动设计不仅告诉我们软件的问题域的重要性,同时也提供了一种分析领域的技术方法论,但是目前我觉得只是按照领域驱动设计那本书的实体,值对象,领域服务,聚合,仓库这些概念不够,比如什么样子的行为属于实体,聚合跟,什么样子的行为属于领域服务,如何界定,如何去划分,这个有时候很难把握,这也许就是建模艺术性的体现吧,不能以对错来区分,只能通过不断的重构,优化使其更加接近领域实质。

PS:最后还请flyzb分享一下,你在实战过程中采用的建模技术,比如怎么判断逻辑属于实体,还是领域服务?还有领域服务和四色原型中的角色以及MI模型的联系等,我目前觉得思想原型中的角色概念很重要,它是逻辑的拥有者,而具体逻辑的实现者应该是MI去实现,实现的过程中MI也许还会包含MI-detail。

先谢了CBM介绍,刚入道的我很需要对这方面的知识面扩展,否则很难交流。

其实大家都很注重建模的,因为建模的合理性和科学性,直接影响软件灵活性和扩展性。而四色是一种对模型的分析方法,DCI是则是一种设计方法。建模必须经过分析和设计的,模型是变化的,所以我们需要自己认为一种比较好的分析和设计方法来指导建模。

而你所说的可能是如何去获取领域模型(也就是客观世界的应用领域,应用边界),这是领域专家所需要的技能。但这方面是需要对一个领域不断积累的,不是一朝一夕的事,但实现的思想和技术可以更快掌握。在掌握一套能实现成品的技术后,去积累不是更好?当然同时更好,但现实不允许(工作需要,我们不是freelancher)。所以为什么这里讨论得更多的是实现。而讨论获取的领域则需要指定是那方面的领域,然后再让这方面专业的领域专家再讨论(一般就是用例图,涉及类的就进入实现了)。(如:借贷,财务等领域不是一般人所能分析得到的,当然一个领域专家所认识的不止一个领域的)只是考虑领域,不考虑实现的话,就像MVC一样,在实现时就会迷惘和困难。
[该贴被SpeedVan于2010-10-20 16:19修改过]
[该贴被SpeedVan于2010-10-20 16:58修改过]

"对于外部而言,如果一开始就调用的user域对象的方法就惨了,因为后来增加的业务应该是user与其他对象协作一起完成的,不能放在User内部,但是放在服务里就没问题了"

严重错误。照你这么说,如果一个领域对象需要和另一个领域对象交互,就必须放到服务里了?
就这个问题来说,如果后来增加审批什么的流程,应该需要采取一些手段(例如参考一些设计模式)去解决他,或则你分析以下,是否审批的流程,就是一个领域模型。

其他的不说了。总之,我并不觉得大项目有多了不起,领域模型的作者,做过多少年的大项目呢?我也不觉得某个成熟的理论会依照上下文的场景而变得不同。

审批流程?我首先想到责任链模式。比起看if else舒服多了,可以把策略和责任链合起来用,把责任流程作为一种策略,不过每次都得从头遍历,流程太长的话···╮(╯▽╰)╭,不过对于流程不稳定的,调整很方便(链嘛)。

而DDD只是“一种”指导思想,你完全可以不用,你可以用你自己的。只是很多程序员觉得不舒服的设计,终于有一个不错的指导了。

还有可以看看Jimmy Nilsson的《Applying Domain-Driven Design and Patterns》,ddd如何与pattern结合起来,不过可惜是.net的,反正是思想,最多读得有点累而已。

大项目有多了不起···大项目对整体,细节,效率,质量,思考,运用,基础,创新,场景,人选都有很高要求,而这些正好用来考验新思想能解决那些和不能解决那些的问题,做大项目,也是对人对团队的能力的全面考验。而我们当中遇到的问题,正是要想如何去解决的,于是思想就会不断进步。我们一般做的项目,能发现多少问题···(负载?并发?甚至可能连复用都···)大项目就像能把问题放大,让问题更加容易发现,更加清晰,更加容易理解。