简明现代魔法 -> 编程思想 -> 关于“领域驱动设计”

关于“领域驱动设计”

2009-11-23

Eric Evans所著的《领域驱动设计》(Domain-Driven Design:通常简称为“DDD”)一书可以说是经典中的经典,虽然“领域”的概念早就存在,但是直到这本书的出现,才让人们真正开始认真审视软件的构建,相信你看了这本书后会真正体会领域的力量,也正是这个力量决定了软件最终的价值。

领域的含义

简单的说,每个软件程序都会与其用户的活动或兴趣相关,其中使用程序的主要环境称为软件的“领域”。

领域中形形色色的业务逻辑构成了软件丰富多彩的行为。举例来说,银行财务系统中,领域逻辑就包括了诸如开户,转帐等等操作。可能你会说,普通程序员很少会接触银行系统,这样的例子不够浅显,那我举一个更常见的例子,大凡程序员应该都接触过文章管理系统,它里面的置顶,加精等操作就是领域逻辑。这样看来,似乎用例对应的动作都是领域逻辑了,但是答案是否定了,比如说,文章管理系统中保存文章往往就不是领域逻辑,因为它只是一个和持久化相关的动作而已,是纯粹的技术实现,但是银行财务系统中的保存现金通常却被划为领域逻辑,因为它就是我们常说的存款,有明确的业务含义。看到这,似乎大家又有些Faint了,这里给出一个判断是否是领域逻辑的原则:就是这个逻辑动作是否有明确的业务上的含义,或者说是否是业务相关的,而不仅仅是技术相关的。

只有将技术实现手段从领域问题中剥离才能保证领域本身的精炼,保证程序员可以把精力集中到领域问题本身上来,而不会满脑子都是技术实现手段。

领域的组成

按照Eric的表述,通常将领域中的组成角色分为以下五种:

领域的思考

下面针对上面介绍的五种领域角色来逐一讨论。

实体的概念是比较好理解的,这样的例子很多,比如说每一个人都可以看作是一个“与众不同”的实体,我之所以用与众不同这个词是为了强调实体必须是能够唯一标识出来的,即便是在我们看作长得一模一样的双胞胎,他们也是能更根据一些标识来区分开,比如指纹,可能你会抬杠,要是没有手的残疾人怎么办?那样我们还可以使用DNA检测,当然,这些都是笑谈了,实际编程的时候,一般是使用一个自增数来作为标识,比如在MySQL数据库中保存实体的时候可以使用anto_increment属性的自增字段。需要注意的是如果想判断两个实体是否相等,不能根据实体的属性来判断,必须绝对依赖实体的标识,十年前的你和现在的你虽然在身高,体重,年龄等众多重要的属性中多或多或少的发生了变化,但你还是你,因为你的DNA不会因为这些属性的变化而变化,如果期间你的DNA发生的变异则属于意外情况,这里不考虑。这些理解起来似乎有些哲学的味道了。

值对象的含义,老实说相对实体来说比较模糊,很多人喜欢把数据传输对象也称为值对象(数据传输对象和我们这里说的值对象是有差别的)让人们对值对象的理解产生过很多歧义,而且值对象的例子不如实体那么直接。从字面上来理解,值对象没有唯一标识,大多数情况下,值对象是不变的,所以系统不用实时的跟踪他们,用的时候就实例化一个,不用的时候就销毁,但是,什么时候使用值对象?把哪些属性划为值对象?值对象的作用是什么?这些都是值得考虑的问题。通常来说,当我们进行领域建模的时候,优先把唯一标识和经常用来检索对象的信息作为实体的属性,而其他信息根据相关性或者划分到其他实体中,或者划分为值对象,举例来说:一个CMS系统中,对于文章实体而言,文章编号,文章标题等都应该作为文章实体的属性存在,而对于文章有效性期限的开始时间,结束时间两个信息则应该被放在一个独立的值对象中,这其中,只有开始时间或结束时间,或者开始时间和结束时间同时都存在或不存在,会代表不同的逻辑意义,合理使用值对象,既有利于屏蔽一些相关逻辑的复杂性,也可以保持实体对象的简洁。

工厂相对与前两者会好理解的多,毕竟从名字上就能体现出它的职责,那就是创建对象。既然是创建对象,那我们直接实例化一个不行么?简单的情况是可以的,但是工厂往往会带来巨大的好处,简单的说就是屏蔽了创建对象的复杂性。领域创建对象强调的关联,一组相关的对象应该被看作一个整体,对于其中任何对象的访问也应该从这个整体的“根”开始(通常整体中最重要的实体作为根),所以复杂的关联必然会使创建过程同样复杂起来,那我们可不可以在“根”实体的构造函数中完成对象的组装呢,简单的情况可以,复杂的不合适,比如说组装汽车,通常是在工厂里由组装工人和机器人来操作完成,如果我们在“根”的构造函数里完成组装,无异与在汽车里配备了组装工人和机器人,这当然是不必要的,汽车一旦组装出厂,就不需要组装工人和机器人了,此时再附带他们是一种累赘。

仓储的概念和一些人常说的数据访问对象(DAO)有些类似,但是并不等同,二者一个很大的不同是仓储有“根”的概念,而数据访问对象往往是按照数据库的表来划分的。使用仓储主要是为了查询和持久化领域对象,而领域对象之间往往会有复杂的聚合关系,为了保证不变量,所以才引入根的概念,对领域对象中某个子对象的访问必须通过根来导航。这样说可能不易理解,我举一个简单的例子:轿车,轮胎可以看成是一个领域对象的聚合,轿车是这个聚合的根,如果我们想访问轮胎,必须通过轿车的导航来进行,为什么如此规定,因为轿车和轮胎之间存在一个不变量:一个轿车有四个轮胎,如果允许客户端直接访问轮胎,那么就很难保证此逻辑不被破坏。

服务这个名词被用过很多次了,但是以前人们说的服务大多是从技术角度而言的,从分层来看属于应用层。一般是诸如注册成功发送一个邮件之类的东西,领域驱动设计中的服务不是这个范畴的概念,它强调的是实体之间的相互关系,而不是纯粹意义上的技术手段。举一个例子来说:CMS系统里,如果一篇文章被加入精华,则文章作者的经验值加一。此逻辑中涉及两个实体:文章实体和作者实体。经验值加一的逻辑不管是建立在文章实体里,还是作者实体里都显得冗余,所以有必要在实体之上在抽象出一个服务层来处理。这里可能有人会问:这样的逻辑我们放到传统意义上的应用层不行么?那样做不能说不行,但是多数情况不好,因为此逻辑属于领域逻辑,而不是应用逻辑,如果放在应用层,领域逻辑就外泄了,领域层也就成为了摆设,但是也有例外,有时候我们可能一时很难分辨一个逻辑是领域逻辑还是应用逻辑,这个时候把此逻辑加入到应用层是没有问题的,如果以后发现其作为领域逻辑更合适的话再重构不迟。

专访Eric Evans:领域驱动设计最新进展

为什么领域驱动设计一直都很重要?

基本上,领域驱动设计是我们应该专注于用户所关心领域里的重要问题的指导原则。我们的智慧应该用在理解这一领域上,和那个领域的其他专家一起将它抽象成一个概念。这样,我们就可以应用这个抽象出来的概念构造强大而灵活的软件。

它是一个永远不会过时的指导原则。不论我们何时操作一个复杂的领域,它都有用。大趋势是软件会应用于越来越复杂的问题,越来越趋近于业务的核心。对我来说,这一趋势好像中断了很多年,因为Web突然出现在我们面前。人们的注意力被从富于逻辑和艰深的解决方案上移开,因为有太多的数据需要传递到Web上,只需要简单的动作即可。因为太多的数据要传递,但短时间内在Web上做这一简单的事情又是比较困难的,所以消耗了软件开发的所有能力。

但是现在人们大步跨越了这一Web应用的基本层次,又把注意力集中在业务逻辑上了。

最近,Web开发平台逐渐成熟,足以应用领域驱动设计来做Web开发,有很多积极的信号,比如,SOA,如果应用的好,就可以提供给我们一个非常有用的解析领域的方法。

同时,敏捷过程也有了足够的影响力,大多数项目现在多少都意识到了迭代、和业务伙伴亲密协作、应用持续集成和在强沟通环境下工作的重要性。

所以领域驱动设计在未来会越来越重要,目前已经有了些基础。

技术平台,像Java、.NET、Ruby或者其他的,一直在变化。领域驱动设计如何适应这一情况?

实际上,新的技术和流程应该由它们是否支持团队专注于他们的领域来验证,而不是置领域于不理。领域驱动设计不依附于哪一个特定的平台,但是有些平台为创造业务逻辑提供了更多的好方法,有些平台更加专注。最近几年的发展显示,为创造业务逻辑提供方法的平台是一个有希望的方向,尤其在可怕的20世纪90年代后期。

Java是这几年默认的选择,从表达角度看,它是一种典型的面向对象语言。对于Distracting Clutter来讲,基础语言是个不错的选择。它有垃圾收集功能,实践证明这一功能很有用(这一点是相比于需要非常关注底层细节的C++而言的)。Java语法有些地方比较乱,但是POJO(Plain Old Java Objects)仍然可以设计的让人理解。Java 5语法的一些创新之处提高了代码的可读性。

但是在J2EE框架初次出来时,完全将那些基本的表达淹没在大量的框架代码之中。根据早期的契约(比如EJB Home,为所有变量写的Get/Set前缀存取等)生产出可怕而糟糕的对象。这一工具是如此笨重,使得开发团队不得不全力以赴才能让它工作。而且改变对象非常困难,一旦产生的大量代码和XML出现问题,人们往往束手无策。这样的平台很难开发出高效的领域模型。

另外就是使用初级的第一代工具勉强开发藉由Http和Html(不是为此目的而设计的)来完成的Web UI。在那个时候,创建和维护一个像样的UI是如此困难,以至很少有人去关注有着复杂内部功能的设计。有意思的是,恰在这时,对象技术出现了,很好地解决了复杂建模和设计的问题。

这一情况在.NET平台上也是如此,有些事情处理的比较好一些,有些还更糟糕。

那是一个让人沮丧的时代,但是在过去的四年里,总算有了些转变。首先来看Java,关于怎样有选择地使用框架社区里有了新的看法,很多新的优秀的框架(多数是开源的)也应运而生。比如Hibernate和Spring这些框架可以用一种更轻量级的方法处理J2EE曾试图做到的特定工作。像AJAX这样试图解决UI问题的方法也更加便捷。现在的项目在选择使用可以提供价值和混合的新J2EE项时也更加聪明。术语POJO就是在这时产生的。

结果是项目的技术贡献逐渐明显地减少,在把业务逻辑和系统的其他部分隔离方面也有了明显的进步,这样逻辑就可以基于POJO来写了。这不会自动产生领域驱动设计,但是它提供了一个可行的机会。

这就是Java世界。然后就是像Ruby这样的新来者。Ruby有表达力很强的语法,在这一基础层面上它会是领域驱动设计的一个很好的语言(尽管我没有听说在那些应用程序中有哪些实际案例)。Rails带给人们很多兴奋的地方,因为它最终好像能够使得开发Web UI像上世纪90年代初期在Web出现之前开发UI时一样简单。很快,这种能力就被大量使用在构建众多没有太多领域背景的Web应用上,因为就是开发这些简单的应用在过去来说也是很困难的。但是我的希望是,当问题里的UI实现部分减少时,人们可以把这看成专注领域的机遇。如果Ruby使用是从这个方向起步的,那么我认为它会为领域驱动设计提供一个很棒的平台。(一些基础设施可能要被添加进来)

更多前沿的话题发生在域描述语言(DSL)领域,我一直深信DSL会是领域驱动设计发展的下一大步。现在,我们还没有一个工具可以真正给我们想要的东西。但是人们在这一领域比过去做了更多的实验,这使我对未来充满了希望。

现在,我所能说的是大多数尝试使用领域驱动设计的人们是基于Java或者.NET平台,也有少部分在Smalltalk上。在有直接效果的Java世界里,这是一个积极的信号。

从你写完这本书,在领域驱动社区里发生了那些值得注意的事情?

一个让我很兴奋的事情是,人们采用我在书中提到的原则然后用一些我所没有预料到的方法实践。比如,在挪威国家石油公司的StatOil项目中对战略设计的使用。那儿的一个架构师根据他的经验写了一篇报道。

在其他项目里,还有人做了上下文映射,并应用到在抉择是自己构建还是购买时到对已有软件的评估当中。值得一提的是,我们中的有些人已经通过开发一些许多项目中要用到的基础领域对象,探索这方面的话题了。

我们一直在探索在仍然用Java实现对象时,究竟能把这种域描述语言推动多远。

已经走了很远。在人们告诉我他们在做什么事情的时候,我一直都很感激。

请您给要学习领域驱动设计的人一些建议?

读我的书!;-)另外,在项目中舍得投入时间和资金。我们最初的目标之一就是提供一个好的案例,让人们可以通过它进行学习。

要谨记一点的是领域驱动设计主要是由团队来操作,所以你也许要成为一个布道者。现实一点讲,可能需要你找到一个大家正在努力完成的项目。

另外还要注意下面几个领域建模时的重要之处(Pitfall):

注重实践。模型需要代码。

专注于具体场景。抽象思维需要落地于具体案例。

不要试图对任何事情都进行领域驱动设计。画一张范围表,然后决定哪些应该进行领域驱动设计,哪些不用。不要担心边界之外的事情。

不停地实验,期望能产生错误。模型是一个创造性的流程。

随机文章推荐
网站分类


注:如需转载本文,请注明出处(原文链接),谢谢。更多精彩内容,请进入简明现代魔法首页。

进入新博客
喜欢本文,就分享它吧
给我留言
您的名字:
您的邮件:
您的网站:


 

copyright © 2009 简明现代魔法    学习、分享、进步

power by Gonn 感谢所有关心和支持本站的朋友们