1. 何为“领域驱动设计”
软件开发通常被应用到真实世界中已经存在的自动化流程,或者给真实的业务问题提供解决方案,即要自动化的业务流程或者可以用软件解决的现实问题。从一开始,我们就必需明白软件脱胎于领域,并跟领域密切相关。
软件是由代码最终构成的。也许我们被代码所诱惑,在它上面花费了太多的时间,将软件看作是简单的对象或者方法。
假设以汽车制造来类比。参与汽车制造的工人会专门负责汽车的某个部件,但这样做的后果是工人们通常对整体的汽车制造流程缺乏了解。他们可能将汽车视为一大堆需要固定在一起的零件的集合体,但一辆汽车的意义远不只于此。一辆好车起源于一个好的创意,开始于认真制定的规格说明,然后再交付给设计。经历若干道设计工序,(历经岁月),用上几个月甚至几年的时间去设计、修改、精化直至完美,直至它反映出最初的愿景。设计的过程也不全然是在纸上进行的。许多的设计工作包括制模、在极端条件下对它们进行测试,以验证它们是否能工作等。设计会根据测试的结果做出修改。汽车最终被交付到生产线上,在那里,所有的部件已经就绪,然后被组装到一起。
软件开发也是一样。我们不能直接坐下来敲代码。当然也可以这样做,在开发价值不大的软件时。但我们不能用这种方法开发复杂的软件。
为了创建一个好软件,你必须知道这个软件究竟是什么。在你充分了解金融业务是什么之前,你是做不出一个好的银行业软件系统的,你必须理解银行业的领域。
没有丰富的领域知识能做出复杂的银行业业务软件来吗?没门。答案永远是否定的。那谁了解银行业业务?软件架构师吗?不,他只是在使用银行来保护他的财产安全,以保证他的急时所需;软件分析师吗?也不是,他只精通于如何运用所有能够获得的必要因素去分析一个给定的主题;软件开发人员?别难为他了。那还有谁?当然是银行的业务人员了。银行业务系统被银行的内部人员所熟知,我们称其为专家。他们知道所有的细节,所有的困难、所有可能出现的问题,以及所有的规章等。这些就是我们永远的起始点:领域。
在启动一个软件项目时,我们应该关注软件涉及的领域。软件的最终目的是增进一个特定的领域。为了达到这个目的,软件需要跟要它服务的领域和谐相处,否则,它会给领域引入麻烦,产生障碍、灾难甚至导致混乱等。
我们怎样才能让软件和领域和谐相处呢?最佳的方式是让软件成为领域的反射(映射)。软件需要具现领域里重要的核心概念和元素,并精确实现它们之间的关系。软件需要对领域进行建模。
如果某人不了解银行业业务,他可能需要阅读很多针对领域模型的代码。这是非常重要的。随着时间的推移,没有植根于领域的软件不能很好地应对变化。
所以我们从领域开始着手。接下来要做什么呢?领域是世界中的某些事物,不要企图能轻而易举地捕获它们,以为敲几下键盘就能出来代码。我们需要建立领域的抽象。当我们跟领域专家交流时,我们会学到好多领域知识,但这些未加工的知识不能被容易地转换成软件构造,除非我们为它建立一个抽象——在脑海中建立一个蓝图。开始时,这个蓝图总是不完整的,但随着时间的推移,经过不断的努力,我们会让它越来越好,让它看上去越来越清晰。这个抽象是什么?它是一个模型,一个关于领域的模型。按照 Eric Evans的观点,领域模型不是一幅具体的图,它是那幅图要极力去传达的那个思想。它也不是一个领域专家头脑中的知识,而是一个经过严格组织并进行选择性抽象的知识。一幅图能够描绘和传达一个模型,同样,经过精心编写的代码和一段英语句子都能达到这个目的。
模型是我们对目标领域的内部展现方式,它是非常必须的,会贯穿设计和开发的全过程。在设计过程中我们记住并会对模型的很多方面进行引用。我们周遭的世界中有着海量的内容等待着我们的大脑去处理。甚至一个特定的领域所包含的内容都远远超出了人脑一次可以处理的范畴。我们需要组织信息,将其系统化,把它分割成小一点的信息块,将这些信息块分类放到逻辑模块中,每次只处理其中的一个逻辑模块。我们需要忽略领域中的很多部分,因为领域包含了如此之多的信息,不能一下子放到一个模型中。而且,它们当中的很多部分我们也不必去考虑。这对它自身而言也是一个挑战:
要保留哪些内容放弃哪些内容呢?这在软件建立过程中属于设计部分的范畴。银行业软件肯定会跟踪客户的住址,但它决不会关心客户的眼睛是什么颜色的。这是个很明显的例子,但其他的例子可能不会如此明显。
模型是软件设计中最基础的部分。我们需要它,是因为能够用它来处理复杂问题。我们对领域的所有的思考过程被汇总到这个模型中。这样甚好,但它必须要超越我们的头脑,如果它止步不前,其实并不会有多大的作用,不是吗?我们需要就这个模型跟领域专家进行交流,跟资深的设计人员进行交流,跟开发人员进行交流。模型是软件的根本,但我们需要找到一些方法来表现它,让它和其他事物交流。在这个过程中我们并不是孤立的,所以我们需要彼此共享知识和信息,而且我们需要把它做得更好、更精确、更完整,没有二义性。要做到这点有多种方式。常见的一种方式是将模型图形化:图、用例、画和图片等。另一种方式是写,我们会写下我们对领域的愿景。还有一种方式是使用语言,我们能够也应该针对要交流的领域内的特定问题建立一种语言。在以后的章节中我们会详细讲解它们,但主要观点是说,我们需要用模型来交流。
当我们有一个表现的模型时,就可以开始做代码设计了。这跟软件设计有很大的不同。软件设计类似于构建房子的架构,那是跟一个总图相关的。从另一个方面讲,代码设计是非常细节性的工作,类似于在一面墙上定位一幅油画。代码设计也是非常重要的,但它却不象软件设计那样基础。一个代码设计中的错误通常更容易修正,但要想修复软件设计中的错误需要更多的成本。要想让一幅油画向左边移动非常简单,而想要拆除房子的一个边却是一个完全不同性质的事情。话虽这么说,缺少了良好的代码设计,最终产品肯定好不到哪儿去。代码设计模型唾手可得,当需要时,它们会被很好地应用。优良的编码技巧可以帮助我们建立清晰、高可维护性的代码。
软件设计有不同的方法,其中之一是瀑布设计方法。这种方法包含了一些阶段。业务专家提出一堆需求同业务分析人员进行交流,分析人员基于那些需求来创建模型并作为结果传递给开发人员,开发人员根据他们收到的内容开始编码。在这个方法中,知识只有单一的流向。虽然这种方法作为软件设计的一个传统方法,这么多年来已经有了一定级别的成功应用,但它还是有它的缺点和局限。主要问题是业务专家得不到分析人员的反馈信息,分析人员也得不到开发人员的反馈信息。
另一个方法是敏捷方法学,例如极限编程(XP)。这些方法学是不同于瀑布方法的一堆动作,产生背景是预先很难确定所有的需求,特别是需求经常变化的情况。要想预先创建一个覆盖领域所有方面的完整模型确实很困难。需要做出很多的思考,而且开始时常不能看到涉及到的所有的问题,也不能预见设计中某些带有负面影响或错误的部分。敏捷方法试图解决的另一个问题被称为“分析瘫痪”,团队成员会因为害怕做出任何设计决定而无所事事。尽管敏捷方法的倡导者承认设计决定的重要性,但他们反对预先设计。相反,他们使用大量灵活的实现,通过由业务涉众持续参与的迭代开发和许多重构,开发团队更多地学习到了客户的领域知识,从而能够产出满足客户需要的软件。
敏捷方法也存在自己的问题和局限:他们提倡简单,但每个人都对“简单”的意义有着自己的观点。同时,缺乏了真实可见的设计原则,由开发人员执行地持续重构会导致代码更难理解或者更难改变。虽然瀑布方法可能会导致过度工程,但对过度工程的担心可能会带来另一种担心:害怕做出深度、彻底的设计。
本书描述了领域驱动设计的原则,应用这些原则会增进对领域内复杂问题进行建模和实现的开发过程能力。领域驱动设计结合了设计和开发实践,展示了设计和开发如何协同工作以创建一个更好的解决方案。优良的设计会加速开发的过程,而开发过程中的反馈也会进一步优化设计。
构建领域知识
让我们考虑一个飞机飞行控制系统项目的例子,看领域知识是如何被构建的。
在一个给定的时刻,空中四处会有成千上万的飞机。它们会朝着各自的目的地按照自己的路线飞行,很重要的事情是要确保它们不会在空中发生碰撞。这儿我们不会试图演化成一个完整的交通控制系统,只会关注其中的一个小的子集:飞行监控系统。这个提到的项目是一个监控系统,它会跟踪在指定区域内的任意航班,判断班机是否遵照了预定的航线,以及是否有可能发生碰撞。
如果按照一个软件开发的视角,我们应该从何处开始呢?在前面的章节,我们说过会从理解领域开始,在本例中就是从空中交通监控开始。空中交通控制人员是这个领域内的专家。但是控制人员不是系统的设计人员或者软件的专家。你不能期望他们会交给你一个关于他们问题域的完整描述。
空中控制人员对他们的领域拥有广博的知识,但为了能构建模型你需要提取基础信息并归纳它。当你开始跟他们讨论时,你会听到很多关于飞机起飞、着陆、飞机升空和碰撞危险、飞机请求允许着陆等知识。为了找到看似杂乱无章的信息中的规律,我们需要从某个地方开始。
控制人员和你自己都认同每一个飞行器有一个起始机场和目的机场。所以我们找到了“飞行器”、“起始机场”和“目的机场”,见下图。
那么,飞机从某地起飞又在另一地降落。但空中发生了什么?班机会按照什么路线航行?事实上,我们更关心的是它在航行时所发生的事情。控制人员说会给每架飞机指派一个飞行计划,飞行计划会用来描述假定的整个空中旅行。当听到“飞行计划”时,你可能会在你脑海中想到这是一个飞机在空中必须遵守的路径。在后边的讨论中,你会听到一个有趣的词:路线(route)。它能很快引起你的注意,这是个很好的理由。路线包含了飞机航行中的一个重要概念。
那就是飞机在飞行时要做的事,它们必须遵照一条路线。很明显飞机的出发点和目标点也就是路线的开始和结束点。所以,不同于将飞行器与出发点和目标点管理,看上去更自然的是将它与“路线”进行关联,然后路线再与适当的出发点和目的地关联。
跟控制人员交流飞行器需要遵照的路线时,你会发现路线由小的区间段组成,这些区间段按照一定的次序组织起来就会构成从出发点到目的地的一条曲线。这条线被假定穿过预定的方位点。所以,路线可以被考虑成一系列连续的方位点。从这个角度看,你不会再将出发点和目的地看作是路线的结束点,而只是将它们看作那些方位中的两个点。这跟从控制人员的角度来看可能有很大的不同,但这是一个必要的抽象,对后续的工作会产生帮助。根据这些发现产生出来的变化结果如下:
这副图显示了另外一个元素,事实上路线中需要遵照的每个方位是一个在空间中的一个点,它表现为一个 3 维的点。但当你跟控制人员交谈时,你会发现他并不按这种方式思考。实际上,他将路线看作是飞机航班在地球上的映射。方位只是地球表面上的点,可以由经度和纬度来决定。所以正确的图是:
实际上发生了什么呢?你和领域专家在交谈,你们在交换知识。你开始问问题,然后他们回应。当他们这样做时,他们从空中交通领域挖掘出基础性的概念。那些概念可能看上去未经雕琢、没有经过组织,但它们却是理解领域的基础。你需要尽可能多地从专家处学习领域知识。通过提出正确的问题,正确地处理得到的信息,你和专家会开始勾勒领域的骨架视图,也就是领域模型。这种骨架视图既不完整也不能保证是正确的,但它却是你需要的开始点,可以尽力判断出领域的基础性概念。
这是设计中很重要的一点。通常,软件架构师或开发人员都会和领域专家之间进行很长时间的讨论。软件专家希望从领域专家那里获取知识,他们也不得不将它们转换成有用的形式。从某种角度上讲,他们可能希望建立一种早期的原型以验证它是否能按照预期工作。在这当中,他们可能会发现一些自己模型或者方法中的某些问题,可能想改变模型。交流不再是从领域专家到软件架构师再到更后面的开发人员的单向关系,它存在着反馈,这会帮助我们更好地建立模型,获得更清晰更准确的对模型的理解。领域专家掌握很多的专业技能,只是他们按照特殊的方式组织和使用这些知识,而这通常对要将它们实现到软件系统中不是件好事情。
通过与领域专家的交谈,软件设计人员的分析型思维会帮助他挖掘出一些领域中的关键概念,并且帮助构建出可用于将来讨论用的结构,我们将在下一章中看到这种结构。作为软件方面的专家(软件架构师和开发人员)和领域专家,我们会在一起创建领域的模型,这个模型会体现两个专业领域的交汇。这看上去是个很消耗时间的过程,并且确实如此,但是它也应该被这样做,因为软件的最终目的是去解决真实领域中的业务问题,所以它必须和领域完美结合。
本书评论