复杂软件设计之道:领域驱动设计全面解析与实战
上QQ阅读APP看书,第一时间看更新

2.5 业务平台与中台设计

DDD有界上下文属于DDD战略设计的重要概念,前面主要从开发人员角度阐述如何识别有界上下文,本章节从产品经理或业务分析师(BA)角度阐述有界上下文等概念。一个好的产品经理或BA会将整个业务系统作为一个业务平台设计,产品定义为适合于某些角色的一组功能,作为一个业务平台的设计方法,是照顾到产品之间的隔离与联系,将这些产品的设计与用户操作角色、有界上下文联系起来,在平台中思考可以更好地拆分业务,从而能够将“脑洞大开”的创意设计无缝平滑地过渡到软件落地编码阶段。

如图2-20所示,以股票交易终端软件为例。股票交易软件有买入和卖出两个基本功能,买入股票时,需要查询股票资金余额有多少,是否足够买入,而卖出股票时,则需要将卖出股票的金额加入股票资金余额中,因此买入和卖出服务都会用到资金账户服务。在传统单体架构下,开发买入服务和卖出服务的两个团队会在资金账户服务这里重叠。

图2-20 单体架构下的团队重叠

解决此类问题无非有两种方式:让团队A或团队B单独拥有资金账户服务,例如团队A负责,而团队B需要资金账户服务时,则与团队A协商,这种协商无疑会带来沟通成本。笔者曾经参与过一个大型项目,基本上也是关于资金账户的进出管理,因为资金进入流出的流程非常复杂,因此专门设立相应的团队负责。有两个团队都需要使用资金账户的功能,有一段时间,资金账户功能归一个团队,在实施下一个项目时,又归为另外一个团队,依据是谁对资金账户频繁操作就拥有它,无论哪种方式,这两个团队都要同时赶赴现场。实际上,项目实施是逐个上马,首先是资金进入系统,然后才是资金流出系统,那么在实施资金进入系统时,负责资金流程系统的团队就基本空闲着,但是也不是非常空闲,当负责资金进入的团队在实施过程中对资金账户进行修改时,他们就非常紧张,所有的测试都需要重新过一遍。这就是单体架构带来的耦合和复杂性。

因为没有解决此问题的简单方法,所以最终解决方案是拆分这个单体架构,经过几个月或几年的工作,才能将这种单体分成了微服务。

常见的分割单体的方法是定义数据边界:专门成立一个负责资金账户的团队C,一个服务对应一个数据库,数据库不能被其他微服务共享访问,只能通过该数据库对应的服务访问。

这种方法的问题在于:

1)它可能看起来像DDD,但事实并非如此,因为它基于数据,而不是业务知识。

2)它可能看起来像微服务架构,但事实并非如此,因为服务之间的耦合度很高,服务和团队无法实现自治。

实际上这只是一种分布式的单体架构,反而引入了分布式的复杂性,正如有人幽默地说:“恭喜,您将单技术栈的单体变成了n个微服务”,然后发现这些微服务紧密耦合,现在已经有43个不同的技术栈,每个技术栈都有自己的故障模式,“祝您玩得开心!”——单点故障变成了多点故障,单个大地雷变成了多个小地雷。

对于这种普遍存在的微服务转型过程中的问题,建议根据业务领域知识而非单纯数据来划分单体架构,这时候需要产品经理或BA这样的角色主导这种分割。很多人普遍认为微服务转型只是技术团队的事情,这是一种错误的认识,只有技术参与、没有业务领域或产品经理参与的微服务转型大部分可能会失败,并引入更多技术复杂性和技术故障点。

在产品团队和技术团队合作的情况下,通过多次头脑风暴会议,树立建设一个业务平台的目标,从整个业务平台的大视野,根据不同用户角色设计不同的功能。例如,对于股票交易终端,申购新股时,每次进入买入服务调用,都需要输入申购价格,这些价格不像普通股票那样随时变动,而是固定的价格,因此再使用购买普通股票的买入服务就比较麻烦,这时可为申购新股专门设立一个功能:一键申购。

一键申购与普通买入属于针对不同人群的两个产品设计,一键申购针对申购新股,而普通买入针对二级市场普通买入股票。这两个产品可能会需要一些重叠或共享的功能,例如都需要资金账户余额查询功能,对待这种问题,将一个产品看成一个有界上下文,也就是说,产品经理在设计新产品时,将产品和使用者角色以及领域中的有界上下文统一起来,一一对应,将三者对齐。

产品之间共享信息就是有界上下文之间的映射关系,有界上下文映射中虽然有共享内核,但是这种方式与单体架构问题非常类似,共享内核被排除在外,通过领域事件在不同上下文之间异步共享是推荐方式。当然这种方式会造成重复数据,当在有界上下文之间(不同产品之间)共享信息时,应该尽可能支持团队绩效,这意味着有时需要重复知识。这在其他系统中很常见:在浴室和厨房都有洗手池。

因此,在股票这个案例中,买入服务、卖出服务和一键申购对应不同团队,但是没有必要成立专门的资金账户团队或有界上下文,因为在业务平台设计上,使用资金账户的功能只有专门的资金账户转账服务需要,提供股票资金账户与银行账户的转账,以及资金账户交易明细与余额的查询。从业务平台和产品角度看,确实需要这些功能,因此专门成立资金账户团队应对这种产品的实现,而买入服务、卖出服务或一键申购服务中的资金账户余额查询等功能则在这些产品内部实现,无须绕道借助于资金账户团队实施。

从技术角度看,买入服务、卖出服务或一键申购服务如果采取事件溯源等方式,只记录进出事件,而不真正修改资金账户余额,只有在查询等读取操作时,才从这些事件日志集合中遍历所有进出事件,计算出当前余额。这种方式做到了资金进出系统和资金账户系统最大化的解耦,甚至都不用访问资金账户数据库,因为不需要修改资金账户的余额。

资金账户有界上下文需要获取资金账户余额时,会查询资金进出系统发布的进出领域事件,这些领域事件在发生时,已经通过Kafka等可靠消息系统同步复制到资金账户上下文的数据库中。虽然数据重复,但是数据使用的方式不同,重复冗余能提高可靠性,毕竟账户进出事件是真相的唯一来源,对于来源唯一的数据多份复制,提高可靠性,类似日常管理中的归档处理,况且资金账户上下文保存的是历史事件,最新事件还是可以从买入服务、卖出服务等有界上下文中及时读取,至于历史事件和最新事件如何划界则取决于聚合边界的设计。聚合边界如果是按单日设计,那么一天之内的事件都在该聚合所处上下文之中的数据库,而一天以前的事件则已经归档到历史事件,在资金账户当前有界上下文就能直接获取。

如果发现大部分的相关信息暴露给了很多其他产品(有界上下文),这时可以抽象设计更通用的产品,主要针对使用者角色设计该产品,并公开一个更简单的服务;但是如果在整个业务平台找不到对应的使用者角色,则通过数据复制或重复知识方式实现不同产品或上下文之间的解耦。

总之,将有界上下文与产品和角色联系起来,能够从一个更高的高度也就是从业务知识层次去划分团队,提高团队生产效率。其优点总结如下。

1)在平台中思考可以更好地拆分业务。

2)将产品链接到角色和有界上下文,可以使边界明确。

3)事件溯源和事件驱动的体系结构对于构建分布式和可用平台至关重要。

4)团队不应共享代码,而应共享一个公共业务平台,即中台。

中台概念源于芬兰Supercell公司,仅有300名员工,却接连推出爆款游戏。这家公司设置了强大的游戏平台,支持众多小团队进行游戏研发,专心业务创新,这个游戏平台设施被贴上标签:中台。游戏包含游戏内容和技术两个方面,那么这个中台是游戏业务平台还是技术平台呢?大部分人认为是技术平台,例如SOA或微服务平台,甚至大数据平台,其实经过前面的分析发现,如果没有业务平台,微服务还是耦合的,这样的中台是泥球的单体中台,而大数据平台则是用于分析查询。数据来自哪里?应该来自业务领域中的领域事件,因此,基于DDD、事件溯源的业务产品平台才是中台的真正定义。