上文探讨了领域驱动设计核心理念:在业务系统中应该使用与问题域相关的模型,而不是通用的数据结构去描述问题,并且介绍了 DDD 的实现方式——知识消化,讨论了第一个关联,领域和实现关联。本节主要研究什么是统一语言,他有什么作用,我们怎么使用他。
1. 为什么需要统一语言
上节有讲过,知识消化的第一点是将模型和实现关联,模型即实现,修改模型就相当于修改实现,那我可以这样理解,模型既然已经跟实现强关联了,那业务方直接用模型来跟技术进行沟通不好吗?你要改哪里,怎么改,拿着模型直接告诉我,都不用沟通理解了,岂不美哉?理想情况下这样当然是最好的,但是这里有两个小问题需要解决:
- 业务和模型的隔阂
- 模型本身的缺陷
这两个问题容我慢慢道来。
1.1 业务和模型的隔阂
DDD 本质是模型驱动设计,那我们想当然认为,如果业务方能够完全理解模型是最好的,但现实情况却是业务方跟技术看待问题的角度是不一致的,业务方习惯于从业务角度看待问题,比如流程、交互、功能、规则、价值等,而技术人员却习惯从模型角度看待问题,模型侧重于数据角度,主要描述在不同的业务场景下,数据如何变更流转,如何支撑对应的计算和统计。因此指望业务方拿着模型跟你直接说哪哪需要如何改动无异于痴人说梦。
1.2 模型本身的缺陷
上节我们有说过,模型是从已知需求中提炼总结而来的,因此模型无法表示还未提炼的功能和知识,这意味着如果业务直接使用模型和技术人员沟通,那未知的知识双方就没有统一的语言,就无法达成一致。
2. 统一语言的好处
2.1 本质
上面说明了业务方和技术人员直接使用模型来沟通,存在的问题,那怎么解决这些问题呢?我们其实是需要一个相较于模型十分精确而言,相对模糊的允许歧义与未知的隔离层,来帮助我们发现和反馈模型的不足,更进一步,其实我们需要的是一种与领域相关联的共同语言,它既能让模型在核心位置扮演关键角色,又能弥合业务和技术视角的差异,并提供足够的缓冲。
上面的解释可能还是有点拗口,我尝试按照我自己的理解来说明一下,我认为统一语言主要由两部分组成:
- 领域
- 共识
即统一语言是在领域内,各个方向的人员达成的共识,就是统一语言,同一个名词或者一段话对于不同方向的人员,理解应该是一致的,举个简单的例子,在订单领域,业务方说的下单指创建订单,对于技术人员而言,也是创建订单,这是没有歧义的,相反,业务方说的发货指的是货品出库,但对于技术人员而言,发货可能是生成出库单号,这就是双方没有达成共识的非统一语言。
因此,一旦各方确定了统一语言,就能根据统一语言来进行交流而不产生歧义,这样对于业务方和技术人员而言,沟通就变得十分顺畅了。
并且,由于统一语言不是固定的,他是随着模型动态变化的,只要最终能达成共识,那就表示统一语言得到了更新,并且统一语言的特性使得它又能在领域的基础上,表示非领域的知识。
比如当前领域退货没有条件,模型中没有相关的退货条件的知识,此时如果需要增加这部分功能,那么业务方可以通过统一语言,说明什么条件下(基于模型已有能力或者对还未支持的能力进行扩展),订单可退,技术人员跟业务关于订单可退条件达成共识后,模型中就新增了关于订单可退的知识,并且后续业务方在跟技术人员沟通时,直接说可退,就隐含表示订单需要满足某些条件。
2.2 关于控制权
通过上面的解释,你应该能理解统一语言是基于模型的,而在上一讲中,我们又将模型和实现关联了起来,那自然的,统一语言其实跟实现也应该是关联的,实现的变化会导致统一语言的变化,统一语言的变化也能导致实现变更。
这句话看起来很正常,但是细细思考,会发现这句话其实有点恐怖,因为这意味着技术人员重构优化代码,导致实现发生了变化,进而会导致统一语言发生变化,这对业务方而言,就是业务发生了变更,即统一语言赋予了技术人员定义业务的权力。
业务方失去了对业务 100% 的控制权!统一语言让渡了部分业务方的权利!
听起来有点惊悚,但是回过头一想,又释然了,为什么我们会觉得业务方对业务应该 100% 有控制权呢?业务最终实现是技术方来做的,按理说本来技术就应该享有部分业务控制权,只是在没有统一语言前,技术人员代码的变更,很多时候业务方是无法感知的,对业务方而言,这些变更是“隐秘的角落”,这给业务方造成了一种业务尽在掌握的感觉,但其实某些实现已经脱离了业务方的掌控而他们并不自知。
统一语言在给技术人员带来额外权利的同时,也隐含额外的义务,因为业务方借由统一语言的变更,能感知到实现发生了变化,进而会对这些变更做监督和反馈。如果业务方不同意变更,那技术人员必须在后续的知识循环中,对此进行修正,也就是说业务方其实也拥有了部分代码的控制权。
总而言之,言而总之,统一语言提供了一种更好的协作方式,在业务方大多强势的环境中,难能可贵地建立了技术反馈业务的途径,降低了知识消化过程失败的风险。
3. 什么是统一语言
统一语言包含以下内容:
- 源自领域模型的概念与逻辑(基石)
- 界限上下文(子域)
- 系统隐喻
- 职责分层(稳定和不稳定划分)
- 模式与惯用法(业务规则、流程与实现模式)
这里面我想先介绍一下我认为较难理解的界限上下文和系统隐喻。
3.1 界限上下文
界限上下文一听,就不知所云,每个字都认识,连在一起咋就不理解呢。这里我按照我自己的理解尝试通俗的解释一下什么是界限上下文。
首先我会将界限上下文分为界限和上下文。界限就是边界,就是在一定的范围内讨论问题,上下文:上下文其实也表示一定的范围,因此他们合在一起,本质就应该是表示在某个范围内,统一语言才有作用,而这个范围,我简单的将之理解为子域的划分。
比如在订单领域,正单和退单都属于订单,但是同一个词语在这两个子域中表示的含义可不同,比如发货,在正单表示商家寄出货品,在退单则表示买家寄回货品,这就是一个非常明显的在不同范围(子域),相同术语表示不同的含义。
如果我们在不同的范围尝试形成统一语言,也就是将大的领域划分为更为具体的小的领域,最终你发现,这跟子域重叠度非常的高,因此我认为界限上下文跟子域他们本质是相同的。
在不同子域范围内,业务和技术可以分别达成不同的统一语言,这些统一语言合在一起就是领域的统一语言,业务方在跟技术人员沟通交流时,先指定子域(界限上下文),这样就能实现子域语言无歧义。
然后不同界限上下文之间应该是能够独自演进的(正单和退单可分别演进),并且之间的交互协议是需要预先进行定义的(http、rpc 还是 mq),这些也是子域应该具备的特性,所以我才说界限上下文其本质就是子域。
3.2 系统隐喻
系统隐喻也不太好理解,它即是比喻,又不是一般的比喻,它本质是通过类比现实世界的运作机制,构建团队对复杂系统的共享心智模型。
说的简单点就是在现实世界中找一个对象,将复杂的业务系统利用该对象进行比喻说明,将业务系统具象化,降低团队对业务系统理解的难度,同时驱动系统的设计。
举一个简单的例子,我们利用“拍卖系统”来隐喻“股票交易系统”:
统一语言映射
| 业务场景 | 隐喻术语 | 领域事件 | 代码实现 |
|---|---|---|---|
| 挂单 | "举牌出价" | BidPlacedEvent | orderBook.placeBid() |
| 撤单 | "收回竞价牌" | BidWithdrawnEvent | orderBook.cancelBid() |
| 撮合成交 | "落槌定标" | TradeExecutedEvent | matchingEngine.match() |
| 熔断机制 | "暂停拍卖" | TradingHaltedEvent | circuitBreaker.trigger() |
股票交易系统本身是一个非常复杂的系统,但是如果我们用拍卖系统来隐喻,那么对于团队来说,理解难度会大幅降低。
这个例子如果觉得还不能理解的话,那我再举一个更简单的例子,我们产品在不能快速说明某个概念的时候,经常会跟我们说,我们目标就是要做 xx 领域的淘宝、xx 领域的滴滴,淘宝和滴滴是有某些共识的特性的,说明我们系统也会有这些特性,这个例子总能理解了吧。
4. 小结
本节其实是比较抽象的,因为统一语言这个东西也是我们经常在用,但却很少能感知到的一个概念,不用将其想的过于复杂,本质就是领域内的共识。
为什么我们需要使用统一语言而不是直接用模型来沟通呢?主要原因在于以下两点:
- 模型将业务维度隐藏了,对业务方不够直观
- 对于未提取的知识,超出了模型的表达能力。
统一语言给予了技术人员定义业务的能力,也赋予了业务人员指导代码的能力,在业务方大多强势的环境中,难能可贵地建立了技术反馈业务的途径,降低了知识消化过程失败的风险。