对领域驱动设计的初步认识(转-J道)

来源:互联网 发布:淘宝网三星平板电脑 编辑:程序博客网 时间:2024/05/17 23:11

  首先声明,我只是看了《领域驱动设计精简版》和一些DDD文章(包括banq的),想谈谈自己的认识,如有不足希望大家指正。
    “领域驱动设计”,顾名思义,首先强调的是”领域“。这个词不是指技术上的任何东西,而是指”业务领域“,是说用领域的角度去分析和设计业务。
    可是在现实中我们有多少人又真的懂业务呢,那些低层次的程序员就不用说了,因为他们了解的业务甚至都不是第一手的,都是经过架构师们消化过的。至于那些架构师门了解的业务就是正确的吗,我看不见得。因为就中国的国情而言,尤其对于企业开发而言,处处充满着管理的不规范性,用户说的很多需求就是不规范的。按照这样的用户需求做出的软件只能是个怪胎,满足一时之需,时间一长就不能用了。原因是什么呢,因为用户是会逐步成长和规范的。
    那正确的做法应该是什么呢?答案很简单,向领域专家和专业书籍学习。同时要找出当前用户在业务不足之处在哪里,要在规范和现实之间找到一道桥梁。
    ”领域驱动设计“的另一层含义是”驱动设计“。显然,”设计“只是一种手段和工具,是为”领域“服务的。”设计”的过程和质量必须通过”领域模型“来检验和验证,这也是测试驱动的本质。当然,我们要尽可能不要让设计工具本身的缺陷扭曲了“领域”本身,那就本末倒置了。这就是为什么banq要批判“基于关系数据库的业务设计”以及坚持“域模型不要让技术污染了”。
    在生活中也是如此,如果你骑过车或者开过车,你的心中都有一辆你真正属于你的车,至于这个车是不是有轮子什么的都不重要,关键是能让自己舒适地出行。显然”舒适地出行“才是真正的业务领域,而是”轮子之类的“只是设计。
    做企业开发快10个年头了,遇到很多困惑和困扰,在我的心中也渐渐有一个真正的企业模型。对我来说,”领域驱动设计“其实也是一种”设计“。


  对于上一个帖子,大家有一些讨论。有人说“领域驱动设计”非常好,也有人说“领域驱动设计”有问题。其实大家都有道理,我针对大家的讨论进一步谈一下自己的认识。
    先谈谈现实业务中的问题域模型,这个模型不是“领域驱动设计”一书中的“领域”,那个“领域”更侧重强调是“领域模型”。可惜目前似乎还没有一种办法能够去立体化并动态地完整描述这个“问题域模型”。为什么我要强调这个东西,就是让大家想一想我们为什么要需要“领域驱动设计”。其实我们有一个永恒的技术使命,就是要解决“问题域模型”的变化问题,因为“领域模型”会存在与”问题域模型“不匹配的问题,这个可能是时间变化或者用户场景变化的原因造成的。对”领域模型“的检验标准就是看是否能适应”问题域模型“的变化,其中”开闭原则“是在这里适用的。
    所以对"领域驱动设计"的第一层”驱动“的含义,我觉得是"问题域模型"驱动"领域建模"以及"测试建模"。从这一点来说,"领域驱动设计"是适合一切场景的,而有人说”领域驱动设计“有问题,准确地来讲是目前的"领域建模技术"还不完善,当然也可以说是"领域建模技术"的运用有问题。这就像最近有人说"面向对象编程走错了路",其实并不是真的说OO错了,而是说"传统OO"有很大的缺陷,首先第一条就是"没有面向消息的传递机制"。
    对于有人说“领域建模”只适应“稳定领域”,我并不赞同。我认为领域建模的真谛是认清业务发展变化的脉络,其最高境界就是“庖丁解牛”。因为真实世界中并不存在“稳定领域”,业务的变化是一种永恒。如果业务永远不变,那就我们就真的幸福了,只要满足用户需求,怎么设计都可以了。
    关于"领域驱动设计"的第二层含义在上一个帖子里已经分析过了。
    总结一下,"领域驱动设计" = “问题域模型驱动领域建模” + “领域建模驱动软件实现”。
    呵呵,总算买了一本Eric的注释版,后面将真正进入“领域建模技术”的学习,望与大家共勉。另外,我也不赞同在远程交互中传递复杂的领域对象。


首先声明,《领域驱动设计》对我来说只是一种建模技术,我讨论的前提是在面对企业多业务集成(如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.


  从我自身从事企业软件开发历程看,更注重一个“悟”字。由于个人比较喜欢看武侠和玄幻之类的小说,至今每天闲暇之余都是除了看资料就是看小说。从练武的角度看,大家总是在讨论“四色原型”、“类和职责分配、逻辑到底放在服务里还是领域对象里”之类所谓“剑招”的东西,而我更喜欢体悟一些“剑意”的东西。我不懂得很多的剑招,来到这里才知道了“DCI、四色”,开阔了眼界,非常感谢banq和大家。通过学习,我进一步印证了自己在实践中的一些想法,让我的思路更加地清晰。xmuzyu让我谈一下自己的建模技术,其实我只是有一点体会,希望大家批评指正,我非常喜欢在讨论中悟道的感觉。
    “一上来先识别类,然后就考虑怎么分配职责”的做法是一种本末倒置的僵化的静态建模。这种方式往往让人落入一种只注重“形”而忽略“意”(业务本来的面目)的怪圈。我认为建模分为2个阶段,第一个是“有机的业务建模”,第二个是“技术建模”。“业务建模”和“技术建模”的区别是:“业务建模”完全是业务语言,不含任何技术成分,比如“类、值对象和职责”的概念,在“技术建模”中才涉及那些概念。“业务建模”做好了,“技术建模”就成了自然而然的东西了。
    “有机的业务建模”的一个非常重要的特征就是“有机”,就是强调业务模型中的每一个“业务主体”都是一个生命体。这些业务主体都有“生命特征”,在一定条件下受到外界的刺激就会发生一定的行为。这些业务主体构成了整个业务模型。当然,这些业务主体的规模和层次不同:比较大的业务主体就是“领域主体”,他有明确的领域边界;而领域主体的对外界响应其实是通过内部的“业务核心主体”的协作来完成的,有的看见的见摸得着————具有业务实体的特点,有的比较抽象————控制规则和流程,当然有的“业务核心主体”可能是个多核细胞。“有机的业务建模”有一个非常重要的工作就是“检查业务模型的生命健康状况”。需要注意的是这个业务建模可能是通过不完善的用户需求建立起来的,所以我们需要构建多种场景通过不同的刺激去看这个领域主体内部的响应中是否有“异常情况”,如果有那就要开刀了。
    关于“技术建模”,我认为“对内功能和对外功能要分开”,领域对象不建议同时承担对内和对外的功能,但小项目除外。服务对外要封装和隐藏领域内部的东西,提供的是服务接口;同时服务要负责领域内部对象的行为组装。从另外一个层面看,服务是需求,领域对象模型是实现。值对象的本质就是反映业务主体的不同业务特征,可能是业务主体的临时状态(比如用户的发帖数)或者属于另一业务主体的状态值。从微观看,聚合是交互最紧密的业务对象的封装,从宏观看,聚合是一个具有明确业务边界的独立业务核心主体。所以聚合只是形式,业务模型的正确建立才是决定要素。
    另外,我谈一下“面向关系设计”的意义。其实,不要一说“面向关系设计”就是错的。因为用户的角度看,业务本来就是面向关系和过程的,这非常自然;而从设计看,业务是不同主体在相互作用。这就是为什么越靠近用户的地方面向关系和过程的设计味道越浓的原因了。


 一边看着Evans的书,一边学习JiveJdon源码,同时印证着我的经验和想法后有这样一种感觉:DDD就像一颗闪光的明珠,但从目前来讲还不够完美。
    这不禁让我想到一个问题:随着各种新的建模技术出现,我们应该如何去把握?其实在论坛里也看到大家有时也会有很多迷惑,对各种建模技术名词不知道该如何去正确的区分和运用。当然,我自己也有。为了解决这个问题,我们应该知道真正的好的建模应该是个什么样子,以此来验证各种建模技术是否能够达到心中的目标。这就好像那些玄幻小说一样,每一本玄幻都有一套体系的设定,最终的境界都是一个“道”字,都会有明确的境界标准。
    那对于我们做企业业务建模,终极目标应该是个什么样子呢?我认为这个终极目标一定是非常复杂的(也就是我原来常说的大项目场景),因为只有在复杂的场景下才能真正检验各种建模技术的偏颇。想想看,我们的建模目标应该向谁学习。论坛也有人说过,“自然的就是最好的”。是啊,经历过亿万年才进化出来的模型难道不值得我们学习吗,难道不是我们的目标模型吗!呵呵,答案已经呼之欲出了,就是“仿生物建模”。是的,没错,如果说利用我们的建模技术能够去构建出一个复杂的仿真人,具备人的一些特征和功能的话,那这种建模技术就是完美的。记得前面的帖子里,我总是提到大项目建模,有人认同,有人不以为然,还有人茫然。其实离我们最近的最熟悉的大项目就是“人体”,我们可以学着用学到的建模技术去“对人体建模”,去检验你的建模技术,这样我们会少一些迷茫。其实对于像DDD、EDA、DCI、CQRS等这些新技术,你都可以在人体奥秘中找到影子,可以通过人体本身的模型机制去验证这些技术的完备性,从而学会在实践中应该如何正确地去运用这些技术,另一方面也为这些技术的发展指明了方向。
    对我前面几个帖子有一个总结:从层次上来讲,企业的业务建模可以分为两个层面,”宏观建模”和“微观建模”。“宏观建模”是指首先要对企业做一个整体的信息化规划,对企业进行整体的的业务架构建模,其成果就是业务组件。其中的方法论可以参照IBM的CBM,不过IBM好像也只是咨询,真正的落地还要靠自己对CBM的领悟。
    Evans的DDD主要属于“微观建模”部分。对于“微观建模”,我认为分为2个方面:“结构化建模”和“行为建模”,这是一体两面。我觉得Evans对DDD总结了几个关键的要素:实体、值对象、聚合、工厂和存储,但其中还少了一个非常重要和关键的要素:“事件”。虽然banq在JF中引入了事件的设计,但我感觉还不够彻底。众所周知,人体是由很多细胞构成的,那细胞之间是如何作用的呢,其实就是“刺激”和“响应”。其中“刺激”就是“事件”,所以“事件”是业务模型本来就应该具备的要素,而不是什么技术层面的东西。从这点来看,JiveJdon中的用户发帖这个动作激发一个“发帖事件”,这个事件会导致一系列的对象和值对象的状态变化(关于JiveJdon的疑问我会在后面的帖子详谈)。同时,我记得论坛里有道友对职责的划分产生疑问。那首先要明白什么是“职责”,从“事件”角度看,“职责的本质就是事件的响应”。这也就是为什么我说JiveJdon对事件的运用做的不彻底的原因了。
    “结构化建模”是指建模中除了静态的实体和值对象的结构关系外,从“事件”角度看,实体或者值对象还具备一些“本能的反应”,比如"手指会弯曲"。而“行为建模”是指通过神经中枢(消息总线)来控制不同对象的本能反应来完成一个复杂的组合,比如"用手弹钢琴"。


第一次看到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特别强调聚合根的封装性,然而这可能会导致领域内对象之间的逻辑强耦合。也许有人说领域内部的对象是高内聚的,这样做没关系。但是在实战中,领域模型内部的值对象往往存在着变数,这是我们认识客观世界的必然规律。然而这会导致领域模型的不稳定性。所以我认为及时领域内部的对象也应该注意低耦合,这个问题同样需要靠事件来解决,事件才是保证领域对象一致性的关键。

原创粉丝点击