DDD领域驱动设计(一)
本文是《实现领域驱动设计》的读书笔记。该书整体上可以分成两大部分,前一部分是战略设计,后一部分是战术设计。本篇文章是对战略设计的总结。
先捋清楚真正的业务需求
领域是一个组织所做的事情以及其中所包含的一切。简单理解就是一个组织所从事的业务范围。例如金融是一个领域,电商也是一个领域。
采用DDD的目的,就是设计出能够准确表达业务意图的软件模型,并且这个模型是可伸缩的、组织良好的。
要想能够为你的系统设计出这样的好模型,作为开发者,首先你要做的就是深入了解你所在的领域,即深刻理解你的公司所从事的业务。但通常情况下,研发人员只关注技术方面,对业务往往重视不够,
所以DDD首先要讲的并不是技术,而是关于讨论、聆听、理解、发现业务价值。研发人员需要向领域专家聆听和学习,这里的领域专家可以是精通业务的任何人,他们了解更多的关于业务领域的背景知识。
回顾一下在我们日常的开发中,领域专家只将关注点放在业务上,而开发者只注重技术实现。这样在领域专家和开发者之间便会存在鸿沟,无法保证开发者开发的系统完全符合领域专家最初的本意,或者开发者对业务背景了解的不够深入,无法对系统的未来做及时的规划,导致对软件的设计只考虑当前,无法适应需求的变化。
DDD提倡领域专家和软件开发者一起工作、深入交流,这样开发出来的软件才能够准确的表达业务规则(最符合领域专家的本意)。只有让开发者深入理解业务背景,才能够设计出最符合业务价值的软件。
书中用了很长的篇幅强调研发人员要与领域专家一起工作,可真实情况往往比较复杂。作为研发人员,我想了解业务背景知识,可产品也得愿意给我讲才行啊。而且很多产品往往自以为是,提的很多需求往往已经限制了系统的设计,并没有表达出业务的背景和真正意图。所以这部分也就简单了解下,知道作者要表达的东西就行了。
通用语言
在平时工作中,很多概念都比较抽象难以表达。这带来的问题有两个,一是抽象的概念给沟通造成很大的不方便,二是写代码的时候不知道该怎么起名字。这样久而久之,随着人员变动,很多历史业务逻辑就消失了。
我们可以为每个抽象的概念指定一个明确的定义,这个定义可能是业界通用的,也可能是我们团队内部原创的。例如对于一个支付系统来说,账户的概念是会计界通用的,但退款过度户的概念就是根据团队内部自身的需求原创的。在团队内部,我们可以创建一个对业务中各个概念进行定义的术语表。
DDD提倡领域专家和开发者之间应该建立通用语言,通用语言就是为领域中涉及的每个概念建立一个标准定义。通用语言必须在整个团队内达成一致,在团队内部所有人都使用通用语言进行沟通,这样大家彼此交流时,每个人都能直接听懂他人所说的是什么。通用语言也可直接反映到软件模型中。
谈及通用语言时,必须要指定是在哪个限界上下文。相同的词语或句子在不同的限界上下文可能表达不同的概念。所以限界上下文和通用语言间存在一对一的关系,只有当团队成员在同一个限界上下文时,通用语言才是“通用”的。
一个限界上下文不应该非常大,如果你试图将某个通用语言运用在整个企业范围之内,或者更大的,跨企业范围内,你将失败。
子域
通常情况下,一个组织所从事的业务领域范围很大,包含的内容非常的多,我们为整个业务领域创建一个单一的、全功能的模型会非常的困难。所以一般都是先将整个领域拆分成若干个子域,再为每个子域创建领域模型。所以在创建一个领域模型时,我们关注的通常只是整个业务系统的某个方面,即为某个子域创建模型。
先将整个业务领域拆分成若干子域,再为子域设计领域模型。这种模式在计算机中不就是典型的分治法嘛。在计算机领域,这种拆分模式非常常见,例如网络协议的分层,操作系统的分层等等,都是原本一整个领域太过复杂,才将其拆分成不同的子域实现以便降低复杂度。
例如对于一个支付系统,既涉及到用户付款,又涉及到商户结算,还涉及到多种支付方式、记账等等业务逻辑。如果整体考虑,很难创建出正确的业务模型。所以支付宝的实现方式就是将整个支付系统拆分成若干子域,包括会员域、收单域、支付域、账务域、金融域、营销域、资金域、结算域、数据域等等,每个子域都有自己的职责,多个子域需要互相协作才能完成整个业务功能。
如果不对整个领域进行拆分,系统中的各个部分都是紧密联系在一起的,那么模型设计将变得非常复杂。DDD提倡按照实际功能将这些交织的模型划分成逻辑上相互分离的子域,这样在一定程度上可以降低系统设计的复杂性。
一个子域按照类型,可以分为核心域、支撑子域和通用子域。
- 核心域就是整个领域最核心的功能部分,例如对于支付系统,收单支付应该算是其核心域。
- 支撑子域对应业务的某个方面,例如数据域,提供账单的统计。
- 通用子域被用于整个业务系统,例如会员域,虽然不是业务的重点,但所有支付请求都要依赖会员域。
其实我觉得书中这样对子域进行划分没有必要,因为哪个域都同样重要,任何一个子域出现问题都会影响整个业务,所以就不该对子域划分不同的等级。
子域和限界上下文的关系
一个限界上下分不一定只包含在一个子域中,就是说可以有一个限界上下文对应多个子域的情况。但我们通常设计系统时还是尽量让一个限界上下文只对应一个子域。对于一个好的限界上下文,其中的每一个术语应该仅表示一种领域概念。
每个子域都应该有自己独有的通用语言。对于同一个对象,在不同的子域中的概念可能完全不同。例如对于支付系统,在会员域存储的信息叫客户和商户。但在支付域就不能再使用客户或商户这样的概念,而应该使用付款方和收款方这样的概念。虽然都是同一个对象实体,但在不同的子域表达的领域含义完全不同。支付域并不需要知道收付款的参与者是用户还是商户,它只需要知道钱从哪个账户来,去到哪个账户即可。
在《实现领域驱动设计》书中举的例子,用户上下文负责存储用户信息和权限管理,协作上下文负责论坛、讨论等领域。协作上下文就不应该感知用户信息和权限管理等概念。在协作上下文中,关注的是这个用户在协作领域所扮演的角色,例如作者和主持人等概念,而不应该感知主持人是谁,他的权限如何。论坛和“谁可以发表帖子”没有多大关系,论坛只需要知道“有作者发了帖子”就可以了。
上下文映射图
整个领域被拆分成多个子域后,所有子域必须相互协作才能完成整个业务逻辑。子域之间的集成关系用上下文映射图来表示。
在项目的开始阶段,你就应该为你当前的项目绘制一个上下文映射图,即每个限界上下文间的映射关系。
将服务的提供方称之为上游系统,将服务的请求方称之为下游系统。可以这样记忆,请求是从下游到上游,数据是从上游到下游。
上下文映射图总的几个常用概念:
- 防腐层(ACL):上游系统的模型和下游系统的模型可能差别很大,可以在下游系统中建立一层防腐层,防腐层负责向上游系统请求数据,并将上游系统返回的数据翻译成本系统需要的模型。防腐层就是起到一个模型转换的作用,这样系统内部的逻辑和系统外就互不干扰。
- 开放主机服务(OHS):就是系统通过某种协议为其它系统开放的服务接口,让其它系统可以来访问你的服务。
- 发布语言(PL):就是子域之间通信信息的载体,也可叫做媒体数据。例如json、xml。对于sofa这种rpc框架,就是根据某种序列化方法生成的字节流。
如上图所示,身份与访问上下文作为上游系统,它使用OHS和PL在自己系统上开通了对外服务让其它系统调用。协作上下文作为下游系统调用身份与访问上下文提供的服务,在协作上下文内部建立了一层防腐层用于执行网络请求,并将身份与访问上下文的模型翻译成协作上下文自己的模型。使用防腐层使得系统内部和外部互补干涉,达到了解耦的目的。