领域驱动设计之5w1h

      2004年Eric Evans发表Domain-Driven Design –Tackling Complexity in the Heart of Software(领域驱动设计--软件核心复杂性应对之道),简称Evans DDD。距离现在已经十多年�了,DDD在这十多年中不断更新。过去系统分析和系统设计都是分离的,正如我们国家“系统分析师” 和“系统设计师” 两种职称考试一样,这样割裂的结果导致,需求分析的结果无法直接进行设计编程,而能够进行编程运行的代码却扭曲需求,导致客户运行软件后才发现很多功能不是自己想要的,而且软件不能快速跟随需求变化。

本文从 5w1h分析法 一起探索一下�领域驱动设计。

What is DDD

      领域驱动设计,首先并不是关于技术的,而是首先关于讨论、聆听、理解、发现和业务价值的,而这些都是为了将知识集中起来。DDD的目标是创建一个可测试、可伸缩、组织良好的软件模型。如果你了解公司的业务,那么你知道可以为DDD的通用语言(Ubiquitous Language)做出贡献。
      领域驱动开发也是一种敏捷开发过程(极限编程,XP),强调迭代开发。在迭代过程中,强调开发人员与领域专家需要保持密切的合作关系。极限编程假设我们能通过不断快速重构完善设计。所以,对开发人员的要求非常高。
      DDD同时提供了战略上的和�战术上的建模工具来帮助我么设计高质量的软件模型。DDD所建的模型叫领域模型,领域模型是关于某个特定业务的软件模型。 DDD的战略建模工具主要有通用语言(Ubiquitous Language)和限界上下文(Bounded Contexg)。而战术建模工具就比较多了,主要包括但不限于:聚合(Aggregate)、实体(Entity)、值对象(Value Object)、�领域服务(Domain Service)、领域事件(Domain Event)。由于篇幅有限,本文主要�介绍领域建模,更多内容请关注后续文章。

Who & Why

      软件开发中不同的角色遇到的问题:
      新手,初级开发者:“我还年轻,有很多点子,对写代码充满了热情。但是我所在的那个项目简直让人崩溃,我才不想一毕业就被那些重复性的工作纠来缠去。这个项目的架构为什么如此复杂了这到底是怎么呢?我修改了一点代码,却破坏了更多的代码。有人知道这本来应该是什么样子吗?现在,我还得添加--些复杂的新特性。我在遗留代码之上添加了一个适配器来屏蔽那些难看的遗留代码,但是没有用。我相信除了整天写代码和调试外,还有更好的办法。于是有人向我介绍DDD,听说DDD是领域模型中的‘四人帮(Gang of Four)’,不错。”
      中级开发者:“在过去的几个月中,我加入了一个新的项目,这次轮到我来做出改变了。那时,当我和高级开发人员在一起工作时,我发现我缺少对事物的洞察力。有时团队非常涣散,但是我又不知道其中的原因,我决定改变团对成员们的做事方式。我需要一种能够帮我成功的软件开发技术。一个高级架构师想我推荐DDD�,我打算了解一下。
      高级开发者,架构师:“我曾在多个项目中都使用过DDD,不过目前所在项目还未使用。我喜欢DDD战术模式的威力,但是我还打算应用更多的DDD模式,比如战略设计等。在阅读[Evans]时,我发现通用语言的功能非常强大。我已经与团队成员和管理层讨论过采用DDD的事情了;但其中有些中高级开发者对DDD在项目中的前景并不看好,而管理层也不是那么热衷于DDD。我是最近才加入公司的,虽然我是团队带头人,但是整个团队似乎并不愿意被一些新奇玩意儿打断了开发进度。不管如何,我是不会放弃的。虽然其他开发者对DDD没有信心,但是我是能做到的。我决定将领域专家引人到团队中来,使他们跟技术人员一起工作。”
      领域专家:“我已经在IT部门帮他们解决业务问题好长一段时间了,我希望开发者们能够更好地理解他们所做的事情。他们总是认为我们业务人员是愚蠢的。但是他们不知道的是,如果不是我们,他们早就丢了工作。开支者总会以一些奇怪的方式来讨论我们的软件,如果我说这是A,他们却说这是B,好像我们需要有本词典才能交流一样。如果我们试图纠正他们的错误叫法他们就不愿意合作了。我们在这上面浪费了太多的时间。软件为什么不能像真正的业务专家所想象的那样工作呢?”
      软件开发的最大问题之一便是业务人员和技术人员需要某种翻译才能交流。本章将对此做出讨论。而DDD将业务人员和技术人员放在同一个层面上。(在这里的领域专家类似于互联网行业中产品经理的这个角色,当然我说的不是只会画交互图的产品经理,画交互图的事交给交互设计师去做。)
      项目经理:“我们要交付软件,但结果并不总是让人舒心,有时开发时间拖得太长了。开发者在谈论到领域时总是各执一词,莫衷一是。我不确定我们是否需要另一种银弹般的技术或者方法。之前我们不是没有尝试过,但是每次都失败了,结果还是得回到原来的位置。我总是说,我们应该放弃幻想准备战斗,但是团队成员并不这么认为。他们工作非常努力,我总觉得欠他们些什么,于是听听他们的意见吧。他们都是聪明之人,并且都希望做出些改变。对于我来说,如果我上面的管理层同意,我是完全允许开发者门花间学习的。团队成员希望有个集中化的业务知识体系。这样一来我的工作也变得简单,还可以促进团队和业务专家之间的合作和互信。

       上面每个开发角色中遇到的问题,那么总结一下使用DDD可以获得哪些好处:
        使领域专家和开发者在一起工作,这样开发出来的软件能够准确地传达业务规则。当然,对于领域专家和开发者来说,这并不表示单单地包容对方,而是将他们组成一个密切协作的团队。“准确传达业务规则”的意思是说,此时的软件就像如果领域专家是编码人员时所开发出来的一样。
        可以帮助业务人员自我提高。没有任何一个领域专家或者管理者敢说他对业务已经了如指掌了,业务知识也需要一个长期的学习过程。在DDD中,每个人都在学习,同时每个人又是知识的贡献者。关键在于对知识的集中,因为这样可以确保软件知识并不只是掌握在少数人手中。
        在领域专家、开发者和软件本身之间不存在“翻译”,意思是出人家都使用相同的语言进行交流时,每人都能听懂他人所说。设计就是代码,代码就是设计。设计是关于软件如何工作的,最好的编码设计来自于多次试验,这得益于敏捷的发现过程。
        DDD同时提供了战略设计和战术设计两种方式。战略设计帮助我们理解哪些投入是最重要的;哪些既有软件资产是可以重新拿来使用的;哪些人应该被加到团队中?战术设计则帮助我们创建DDD模型中各个部件。

When &Where

      那么在什么时候,什么情况下我们需要使用DDD呢?
      在这些情况下你不需要用到DDD:
      如果你的软件完全以数据为中心、听有操作都通过对数据库的CRUD完成、那么你并不需要DDD。此时你的团队}需要一个漂亮的数据库茬编辑器。换言之,你可以指望着用户对你的数据进行内�直接操作,包括更新和删除数据。休并不需要提供界面。如果你甚至可以用一个简单的数据库开发�工具来完成开发,那么,你完全没有必要在DDD上浪费时间和金钱。
       如果你的系统只有25到30个业务操作,这应该是相当简单的。这意味着你的程序中不会多于30个用户故事(user story)或用例流(use case flow),并且每个用例流仅包含少量的业务逻辑。如果你可以使用Ruby on Rails或者Groovy和Grails来快速地开发出这样的系统,并且你没有感觉到由复杂性和业务变化所带来的痛苦,那么你是不需要使用DDD的。

      以下情况你需要考虑使用DDD了:
      
即便我们的软件目前并不复杂,但是之后呢?在真正的用户开始使用软件之前,我们是无法预测软件的复杂性的,这时有必要和领域专家一起探讨那些复杂的用例。请注意,如果有暗示说明系统已经足够复杂,这往往意味着我们的系统实际上比目前更加复杂,采用DDD吧。
       软件的功能在接下来的几年中将不断变化,而你并不能预期这些变化只是些简单的改变。DDD可以帮你管理软件复杂性,随着时间的推移,你可以对软件模型进行重构。
      你不了解软件所要处理的领域。你的团队中也没有人曾经从事过该领域的开发工作。此时,软件很有可能是复杂的,因此你们应该讨论复杂等级。

       当我们在复杂性问题上犯错时,我们很难轻易地�扭转颓势。
       这意味着我们应该在项目计划早期便对简单性和复杂性做出判断,这将为我们节约很多时间和开销,并免除很多麻烦。一旦我们做出了�重要的架构决策,并且已经在该架构下进行深入地开发,通常我们已经绑定在这个架构下了,所以在决定时一定要慎重。

How

      DDD�最具威力的特新之一:通用语言(Ubiquitous Language)。通用语言和限界上下文(Bounded Contexg)�同时构成了DDD的两大支柱,并且它们相辅相成。
       就目前来看,可以将限界上下文看成是整个应用程序之内的一个概念性边界。这个边界之内的每种领域术语、词组或句子--也即通用语言,都有确定的上下文含义。在边界之外,这些术语可能表示不同意思。通用语言是团队自己创建的公用语言,团队中同时包含领域专家和软件开发人员。

限界上下文和通用语言

      Bounded,即有边界的,表示领域模型有边界;这个边界定义了模型的适用范围,以便让负责该模型的团队知道什么该在模型中实现,什么不该;Context,即领域模型的产生是在某个上下文中产生的;上下文是一个和环境相关的概念。比如某本书中谈到了某个东西,那这个东西的上下文就是那本书,那个东西要有意义的前提离不开那本书这个上下文;所以,上下文是模型有意义的前提;
        自然地,领域专家对通用语言有很大的影响,因为他们最了解业务,并且深受工业标准的影响。但是,通用语言更多地是关于业务本身如何思考和运作的、此外,很多时候,不同领域专家会在概念和术语上产生分歧,他们甚至也会犯错:因为他们也无法了解每种业务用例。因此,当领域专家和开发者一起创建领域模型的时候,他们有时会达成一致,有时会做一些妥协,但最终目的都是为了创造最适合项目的通用语言。

购买和�库存相关的限界上下文示例图--通用语言�作用在特定的上下文中


      那么,你该如何掌握通用语言呢?这里有一些方法:
      同时绘制物理模型图和概念模型图,并标以名字和行为。虽然这些图并不是正式的设计图,但它们却包含了软件建模的某些方面。即使你的团队在使用统一建模语言(UML)来完成正式建模,也不要得意忘形、因为这样可能反而不利于团队的讨论,最终将阴碍通用语言的产生。
       创建一个包含简单定义的术语表。将你能想到的术语都罗列出来,包括好的和不好的,并注明好与不好的原因。在你给术语下定义时,你在不经意间就会创造出一些可重用的词汇,因为此时你使用的是领域中的通用语言。如果你不喜欢术语表,可以采用其他类型的文档,但记得将那些“不正式”的模型图也包含进去。同样,这里最终的目的也是发现通用语言中的术语和词组。
       由于团队中有些人工作在术语表上,还有些人工作在文档上,此时你需要找到团队的其他人员来检查你的成果。分歧肯定是有的,你应该对此有所准备。
       以上是建立通用语言的一些理想化步骤,这样建立起来模型肯定不能直接用来指导开发,而只是建立通用语言的起步而已。此后,改进之后的通用语言将反映到系统的源代码中,比如Java、C#或者Scala等。以上的模型图和文档并未表明通用语言会随着时间而扩大。在通用语言开发早期,这些材料可能会对我们产生鼓舞、但是时间一久,它们也将变得过时。这也是为什么只有团队的交流和代码才能持续到最后的原因,也只有这两者才能实时反映通用语言。

      在理解通用语言时,有必要牢记一下几点:
     
这里的“通用”意思是“普遍的”,或者“到处都存在的”。通用语言在团队范围内使用,并且只表达一个单一的领域模型。“通用语言”并不表示全企业、全公司或者全球性的万能的领域语言。
      限界上下文和通用语言间存在一对一的关系。限界上下文是一个相对较小的概念,通常比我们起初想象的要小。限界上文刚好能够容纳下一个独立的业务领域所使用的通用语言。只有当团队工作在一个独立的限界上下文中时,通用语言才是“通用”的。
      虽然我们只工作在一个限界上下文中,但是通常我们还需要和其他限界上下文打交道,这时可以通过上下文映射图(Context Map)对这些限界上下文进行集成。每个限界上下文都有自己的通用语言,而有时语言间的术语可能有重叠的地方。如果你试图将某个通用语言运用在整个企业范围之内,或者更大的、跨企业的范围内,你将失败。

上下文映射示例图,U表示上游(upstream),D表示下游(Downstream)

      DDD看上去�像一个笨重开发过程。事实上,DDD能够很好地与敏捷项目框架结合起来,比如Scrum。DDD也倾向于“测试先行,逐步改进”的设计思路。在你开发一个新的领域对象时,比如实体或值对象,你可以采用以下步骤进行:
      1.编写测试代码以模拟客户代码是如何使用该领域对象的。
      2.创建该领域对象以使测试代码能够编译通过。
      3.同时对测试和领域对象进行重构,直到测试代码能够正确地模拟客户代码,同时领域对象拥有能够表明业务行为的方法签名。
      4.实现领域对象的行为,直到测试通过为止,再对实现代码进行重构。
      5.向你的团队成员展示代码,包括领域专家,以保证领域对象能够正确地反映通用语言。

       如果你阅读后,想更深入学习领域驱动设计, 可以翻阅《Implementing Domain-Driven Design》(中文译本《实现领域驱动设计》),本文相关资料主要来源于这本书。

更多�精彩文章,请关注 深圳敏捷部落

微信号:sz_agiletour

推荐阅读更多精彩内容