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