数字人民币系统领域驱动设计探索与实践
导读
领域驱动设计(Domain-Driven-Design)作为一种应对软件核心复杂性的方法论,由于其“千人千面”的个体理解差异,一直以来不曾被广泛运用,但随着业务系统复杂度逐渐增加,为重新内聚业务、隔离变化,系统架构开始逐渐向微服务演进,技术界才重新审视和意识到领域驱动设计在应对复杂业务和快速响应业务变化上的价值。本文将分别就领域驱动设计思想的关键技术、应用现状、农业银行在数字人民币系统中的应用实践及领域驱动设计的应用前景展开讨论。
01引言
过去几年,农业银行数字人民币系统一直处于业务能力建设和架构完善阶段,以应对不断增长的业务需求和高并发、高性能、高可用等技术需求。随着数币推广场景愈发丰富,个性化服务接入越来越多。覆盖涉农补贴、智慧出行、供应链、公共缴费等场景,数币业务试点范围也扩大到17个省市。随着业务快速发展需求数量迅速增加,需求内容个性化特征也愈发明显。但同时,系统事务脚本式的架构模式使得系统业务功能之间耦合度开始升高,研发阻力变得越来越大,业务场景在不断丰富然而系统在实现层面响应需求的能力逐渐变弱。由于长期面向数据模型的设计也使得业务功能对数据模型形成了较强依赖。如,因新增一个明细表字段导致所有涉及明细的业务功能都需要全量回归测试,很难快速确认影响范围,准确评估业务模型和数据模型的相互影响变得愈发困难。
为做到“通过业务场景丰富平台能力,同时保证内核干净”的初衷,如何构建业务逻辑和底层基础设施松耦合的系统,打造一个对业务高响应力的架构,开始成为业务团队与技术团队同时面临的一大挑战。因此,我们引入领域驱动设计(DDD),致力于探索一种既能快速响应复杂业务需求、又能对系统架构影响降至最低的解决方案。
02关键技术介绍
领域驱动设计强调以领域为核心,开展业务分析,最终清晰识别业务边界,助力演进式构建系统架构,其中涉及领域(子域)、限界上下文、实体(值对象)和聚合(根)等核心要素。
领域/子域:系统中一系列业务服务的组合,是一系列具有相关性知识的合集,业务价值通过不同领域来体现。比如,数币系统当前包含了账务域、促销域、代收代付域和智能合约域等概念。本环节需要重点关注领域的划分,即如何将一个大的领域划分成若干个子域。
限界上下文:指在一个领域/子域中,我们会创建一个概念上的领域边界,在这个边界中,任何领域对象都只表示特定于该边界内部的确切含义,这样的边界称为限界上下文。限界上下文和领域一般都具有一对一的关系,如支付上下文、退款上下文分别对应支付和退款领域。
实体/值对象:实体对象表示那些具有生命周期并且在其生命周期中会发生状态改变的对象;而值对象仅表示一些起描述性作用并且可以相互替换的概念。实体对象一般具有唯一ID属性,例如订单具有唯一的订单号、用户有唯一的用户编号。
数币系统中每个钱包实体对象都有钱包ID标识,并且具有开立、升级、注销的全生命周期;而钱包开立IP、省、市仅描述了钱包地址信息,应作为一个地址值对象的基础属性;再如,每个币串(纸币)都具有冠字号属性,即使两个金额相等的币串(纸币)也应是两个不同的实体而不是值对象。
聚合/根:聚合描述了一种包含关系,是业务逻辑紧密相关的实体和值对象的组合,它定义了领域模型的职责范围,同时也是数据修改和持久化的基本单元,体现了某个领域对象模型的核心业务能力。
例如,数币促销领域包含了许多和促销业务相关的对象,这些对象按照业务相关性划分后可分为活动类对象、权益(红包)类对象、活动人群(钱包)类对象,在权衡业务封装和适度的职责范围划分后,促销领域包含了活动聚合根对象、权益聚合根对象和人群聚合根对象。分别负责各自业务其他关联对象的管理,如活动聚合根对象又可包含满减活动、补贴活动。权益聚合根又可包含消费红包、现金红包。不同聚合之间通过聚合根关联引用,即外部对象不直接访问聚合根内部的实体。如活动聚合和权益聚合之间通过各自聚合根提供的公共方法交互。
战略设计:面对客户的业务需求,通过领域专家(业务分析师BA)与开发团队(开发架构师Architect)展开充分的交流,经过专业的需求分析与提炼,识别出核心域、支撑域、通用域,并确定领域的边界以及各自之间的关系,最终把一个大的复杂系统设计拆分成多个细粒度、独立和内聚的业务子问题,从而很好地分解和控制业务复杂度并指导微服务的拆分。
战术设计:目标是完成从概念模型到代码落地,即将战略设计阶段的业务概念和关系映射成代码中的业务类和对象之间的关系。经过战略设计阶段后需求被分解成各个子领域,在此基础上战术设计聚焦各个子领域内部完成复杂领域再分解与对象模型的分析与设计,子域内部要求是相对稳定的,未来的变化频率不会很高。同时业务逻辑关联性强的对象进行逻辑划分最终形成一个一个聚合/根,进而构建出每个子域的对象结构全貌。如,促销领域红包分为现金红包和消费红包,在概念模型映射到类模型时现金红包类、消费红包类和红包类的关系可能为继承关系,如图2.1所示:
图2.1战术设计-概念模型到对象模型的映射
领域驱动设计整体流程如图2.2所示,包含战略设计和战术设计两个阶段。
图2.2领域驱动设计一般过程
03领域驱动设计应用现状
近年来,企业业务系统复杂度不断增加,推动了各行业领域驱动设计应用的落地。领域驱动设计以划分业务领域为核心的特质决定了它具备普适性,以战略设计、战术设计为指导使得它能完美支撑建设高业务内聚、模块化特征明显的系统,目前在各行业均有应用落地,以下选择互联网和金融行业介绍。
互联网企业:
以某互联网科技零售公司业务为例,其点评交易系统业务内聚度高,模块化特征明显,这样的业务特点为其实践领域驱动设计提供了前提,如图3.1所示用例。
图3.1点评业务用例分析
在战略设计阶段通过尽可能全面的用例分析与设计,对用例进行合理聚合,由此可以形成系统的概念模型。在此阶段需要完成领域模型的分析并识别出需求中的问题域,此阶段的结束标志着战略设计的完成,其输出将成为战术设计阶段定义聚合、区分实体和值对象的关键,如图3.2所示过程。
图3.2点评业务领域建模
金融行业:
传统银行系统设计中重点关注账户,其承载了财务信息、银行管理、客户协议和风险监管等各种属性信息。以下是某同业账管系统,该账管系统的主要业务功能包括缴费投资、待遇支付、账务处理、收益分配等功能,且这些功能之间相互独立,适合将相关的关键业务分散到不同领域中以彼此隔离,如图3.3所示。根据领域驱动设计的方法,首先从命令和事件中提取产生这些行为的实体;其次根据聚合根的管理性质从实体中找出聚合根;最后划定界限上下文,根据上线文语义将聚合归类。
图3.3账管系统界限上下文划分
领域驱动设计提倡在界限上下文内使用面向对象的设计风格,采用充血模型设计实体对象的属性和方法,同一上下文中这些业务类所包含的业务状态和方法真实地反映了现实需求中相关业务的聚合关系,如缴费申请上下文中经办人对象封装了缴费文件的上传和导入两个方法、复核人对象封装了提交复核和驳回申请两个方法,通过对各自对象方法的调用完成缴费申请状态的转移,通过对具体业务的抽象与整合,不变性业务规则逐渐作为企业资产被累积下来,从而系统具备了良好的业务伸缩性,也就是领域驱动设计元模型中聚合概念的体现。而从编码的角度来看,改变类属性的方法总是离类属性最近,可以最迅速准确地评估出业务需求变更对模型的影响范围。
通过一些简单实践即可发现在引起软件系统复杂度升高的诸多因素中主要因素仍是需求,软件系统需求分为业务需求和技术需求两个方面,业务复杂度跟系统的业务需求规模和需求之间的关系层级有直接关系,需求的数量和关系的层级决定了代码的规模和逻辑循环或者递归的层级,系统的需求越大、需求之间的关系越复杂,系统的业务复杂度越高,John Ousterhout在《A Philosophy of Software Design》(中文译名:软件设计哲学)中提出,软件设计的核心在于降低复杂性,并给出了复杂度量公式,如图3.4所示:
cp:子模块复杂度tp:子模块开发/维护时间占总
图3.4认知复杂度和开发工作量角度定义的软件系统复杂度公式
技术复杂度来自系统运行的质量需求,包括安全、高性能、高并发、高可用和高扩展性。领域驱动设计的核心思想就是避免业务逻辑的复杂度与技术实现的复杂度混淆在一起,确定业务逻辑与技术实现的边界,从而隔离各自的复杂度。与之对应的领域逻辑组织形式还有事务脚本模式、表模式(即面向数据库)。无论采用何种技术,只要业务需求不变,业务规则就不会变化,但不同的模式之间增加的工作量会有所差异。Martin Fowler在《Patterns of Enterprise Application Architecture》(中文译名:企业架构应用模式)中阐述了领域逻辑的复杂性与增加的工作量之间的关系。
图3.5域逻辑复杂性与增加的工作量
如上图3.5所示,虽然在使用领域模型设计的时候可能在前期需要投入更多的时间和精力考虑如何建模,开发中也会遇到一些问题,但随着业务逻辑越来越复杂,它的工作量并没有受很大的波动,而其他两个模式值的变化都很陡峭。
04领域驱动设计农业银行的初步探索
近年来随着数字人民币使用场景快速向各业务领域拓展,农行数币逐渐形成了自己的生态体系,如图4.1所示。与此同时数币系统也经历了需求快速迭代的过程,系统功能逐渐冗杂,研发阻力逐渐增大,随着分布式核心的建设,目前农行数字人民币系统也进行了重构。此次在领域驱动设计方向做了初步探索,引入领域驱动设计方法论指导微服务设计,完成了系统需求分析与设计、领域建模、子域划分以及应用架构选型和落地,提高了系统对复杂需求的响应能力。
面对数币复杂业务场景,数币领域的划分并不是一蹴而就的任务,而是一个动态调整和归集的过程,图4.1展示了数币业务归集后的业务领域划分,核心流程如下:
1.梳理出整个系统的业务子模块;
2.将业务关联性比较强的业务划分到一起,形成各个子域;
3.不断微调,形成一个最终的领域划分。
图4.1数币业务领域归集
对数币当前业务而言,最典型的应用场景是各种优惠促销活动,涉及角色包括客户、商家、补贴方、活动运维管理人员等,客户需要完成活动选择与报名,在正常消费后需要查看消费和退款记录;商家需要发布活动并制定活动规则,活动正式开始前需要完成活动资金划扣到钱包,活动进行中可以选择查看活动信息,活动结束后需要查看对账单、回收资金等。图4.2是促销活动的一个详细用例分析,从用户角度对促销业务进行中系统需要支持的行为以及系统不同功能之间的边界和交互关系进行了梳理。
图4.2数币促销活动用例分析
类比促销领域的需求分析和领域划分思路,本着降低业务复杂度同时分离技术复杂度的初衷,先从拆分业务出发通过用户找到业务场景,再通过头脑风暴的方式识别系统各个领域,重新构建了整个数币系统的业务领域。由于各业务本身存在权重上的差异,依照重要性和功能领域被再次细分为三类,如图4.3所示。
核心域:体现核心服务代表产品的核心竞争力。如,账务子域、库房子域。
支撑域:体现对核心服务的支持能力但达不到核心域的价值,围绕产品内部所需要。如促销子域。
图4.3数币子域划分
聚合是领域内实体对象的管理者,也是领域之间交互的桥梁,一个聚合根一般对应一个领域但考虑具体业务的差异性,即使同一个领域内,也可能包含不止一个聚合根,需要在业务封装粒度和适度范围划分之间做好平衡。图4.4展示了促销领域聚合的一个划分示例。
图4.4促销领域聚合/根
在架构风格方面,数币系统选择了分层结构,与传统的三层架构相比,领域驱动下的分层架构有所变化,首先将属于业务逻辑的关注点放到领域层(Domain Layer)中,而将支撑业务逻辑的技术实现放到基础设施层(Infrastructure Layer)中,应用层(Appplication Layer)的引入扮演了双重角色:一方面作为业务逻辑的外观(Facade),暴露了能够体现业务用例的应用服务接口;另一方面它又是业务逻辑与技术实现的粘合剂,实现二者之间的协作。领域驱动设计通过分层来确保业务逻辑与技术实现的隔离,通过分层架构来隔离关注点,将领域实现独立出来,这样有利于领域模型的单一性与稳定性。
05领域驱动设计在商业银行的应用前景
领域驱动设计的核心价值在于基于特定业务范围,通过统一业务概念将系统各参与方整合在一起,从而减少不同角色和环节的信息熵减问题。虽然目前领域驱动设计思想在商业银行系统建设中运用还很有限,但其以业务领域为核心开展模式设计以达到业务与技术融合的愿景,在商业银行数字化转型的过程中仍然具有应用价值,如在项目需求分析阶段领域驱动设计在业务子域的划分、业务领域对象的识别以及业务领域事件的识别上为我们提供了切入点,为后续以业务为核心开展领域模型设计、打造高响应力架构提供了工具支持。
领域驱动设计并不是架构设计的唯一选项,只是一种面向复杂问题的建模方法和实践,而后续如何打造高响应力的架构,如何让业务与架构更紧密地结合在一起共同推动业务持续发展与创新仍然是我们需要面临的一个挑战。
Scan QR code with WeChat