Skip to main content

DDD实战课-进阶篇

·616 words·3 mins
WFUing
Author
WFUing
A graduate who loves coding.
Table of Contents

06 | 领域事件:解耦微服务的关键
#

在事件风暴(Event Storming)时,我们发现除了命令和操作等业务行为以外,还有一种非常重要的事件,这种事件发生后通常会导致进一步的业务操作,在 DDD 中这种事件被称为领域事件。

这只是最简单的定义,并不能让我们真正理解它。那到底什么是领域事件?领域事件的技术实现机制是怎样的?这一讲,我们就重点解决这两个大的问题。

领域事件

领域事件是领域模型中非常重要的一部分,用来表示领域中发生的事件。一个领域事件将导致进一步的业务操作,在实现业务解耦的同时,还有助于形成完整的业务闭环。

领域事件,可以简单的理解为,当一个聚合完成操作之后,触发下一步的动作。(用事件风暴可以快速识别实体、命令、领域事件) 比如:

  1. 管理员(实体)修改完用户的密码后(命令),需要将用户踢下线(领域事件)
  2. 用户(实体),连续输错密码(命令),将账户锁定(领域事件)

举例来说的话,领域事件可以是业务流程的一个步骤,比如投保业务缴费完成后,触发投保单转保单的动作;也可能是定时批处理过程中发生的事件,比如批处理生成季缴保费通知单,触发发送缴费邮件通知操作;或者一个事件发生后触发的后续动作,比如密码连续输错三次,触发锁定账户的动作。

那如何识别领域事件呢

很简单,和刚才讲的定义是强关联的。在做用户旅程或者场景分析时,我们要捕捉业务、需求人员或领域专家口中的关键词:“如果发生……,则……”“当做完……的时候,请通知……”“发生……时,则……”等。在这些场景中,如果发生某种事件后,会触发进一步的操作,那么这个事件很可能就是领域事件。

如果,假如,等有前因会导致结果发生都可能是领域事件

那领域事件为什么要用最终一致性,而不是传统 SOA 的直接调用的方式呢?

我们一起回顾一下之前讲到的聚合的一个设计原则:在边界之外使用最终一致性。一次事务最多只能更改一个聚合的状态。如果一次业务操作涉及多个聚合状态的更改,应采用领域事件的最终一致性。

领域内高内聚,采用强一致性,领域间低耦合,采用最终一致性

领域事件驱动设计可以切断领域模型之间的强依赖关系,事件发布完成后,发布方不必关心后续订阅方事件处理是否成功,这样可以实现领域模型的解耦,维护领域模型的独立性和数据的一致性。在领域模型映射到微服务系统架构时,领域事件可以解耦微服务,微服务之间的数据不必要求强一致性,而是基于事件的最终一致性。

回到具体的业务场景,我们发现有的领域事件发生在微服务内的聚合之间,有的则发生在微服务之间,还有两者皆有的场景,一般来说跨微服务的领域事件处理居多。在微服务设计时不同领域事件的处理方式会不一样。

1. 微服务内的领域事件

当领域事件发生在微服务内的聚合之间,领域事件发生后完成事件实体构建和事件数据持久化,发布方聚合将事件发布到事件总线,订阅方接收事件数据完成后续业务操作。

微服务内大部分事件的集成,都发生在同一个进程内,进程自身可以很好地控制事务,因此不一定需要引入消息中间件。但一个事件如果同时更新多个聚合,按照 DDD“一次事务只更新一个聚合”的原则,你就要考虑是否引入事件总线。但微服务内的事件总线,可能会增加开发的复杂度,因此你需要结合应用复杂度和收益进行综合考虑。

微服务内应用服务,可以通过跨聚合的服务编排和组合,以服务调用的方式完成跨聚合的访问,这种方式通常应用于实时性和数据一致性要求高的场景。这个过程会用到分布式事务,以保证发布方和订阅方的数据同时更新成功。

2. 微服务之间的领域事件

跨微服务的领域事件会在不同的限界上下文或领域模型之间实现业务协作,其主要目的是实现微服务解耦,减轻微服务之间实时服务访问的压力。

领域事件发生在微服务之间的场景比较多,事件处理的机制也更加复杂。跨微服务的事件可以推动业务流程或者数据在不同的子域或微服务间直接流转。

跨微服务的事件机制要总体考虑事件构建、发布和订阅、事件数据持久化、消息中间件,甚至事件数据持久化时还可能需要考虑引入分布式事务机制等。

微服务之间的访问也可以采用应用服务直接调用的方式,实现数据和服务的实时访问,弊端就是跨微服务的数据同时变更需要引入分布式事务,以确保数据的一致性。分布式事务机制会影响系统性能,增加微服务之间的耦合,所以我们还是要尽量避免使用分布式事务。

rpc或http接口方式,涉及到数据的准备性时,一定要有成对的接口支持保证事物的原子性,即调用者失败后 被调用者必须提供一个补偿接口来撤销之前的调用操作

领域事件相关案例

我来给你介绍一个保险承保业务过程中有关领域事件的案例。

一个保单的生成,经历了很多子域、业务状态变更和跨微服务业务数据的传递。这个过程会产生很多的领域事件,这些领域事件促成了保险业务数据、对象在不同的微服务和子域之间的流转和角色转换。

在下面这张图中,我列出了几个关键流程,用来说明如何用领域事件驱动设计来驱动承保业务流程。

事件起点:客户购买保险 - 业务人员完成保单录入 - 生成投保单 - 启动缴费动作。

  1. 投保微服务生成缴费通知单,发布第一个事件:缴费通知单已生成,将缴费通知单数据发布到消息中间件。收款微服务订阅缴费通知单事件,完成缴费操作。缴费通知单已生成,领域事件结束。
  2. 收款微服务缴费完成后,发布第二个领域事件:缴费已完成,将缴费数据发布到消息中间件。原来的订阅方收款微服务这时则变成了发布方。原来的事件发布方投保微服务转换为订阅方。投保微服务在收到缴费信息并确认缴费完成后,完成投保单转成保单的操作。缴费已完成,领域事件结束。
  3. 投保微服务在投保单转保单完成后,发布第三个领域事件:保单已生成,将保单数据发布到消息中间件。保单微服务接收到保单数据后,完成保单数据保存操作。保单已生成,领域事件结束。
  4. 保单微服务完成保单数据保存后,后面还会发生一系列的领域事件,以并发的方式将保单数据通过消息中间件发送到佣金、收付费和再保等微服务,一直到财务,完后保单后续所有业务流程。这里就不详细说了。

总之,通过领域事件驱动的异步化机制,可以推动业务流程和数据在各个不同微服务之间的流转,实现微服务的解耦,减轻微服务之间服务调用的压力,提升用户体验。

领域事件总体架构

领域事件的执行需要一系列的组件和技术来支撑。我们来看一下这个领域事件总体技术架构图,领域事件处理包括:事件构建和发布、事件数据持久化、事件总线、消息中间件、事件接收和处理等。下面我们逐一讲一下。

  1. 事件构建和发布

事件基本属性至少包括:事件唯一标识、发生时间、事件类型和事件源,其中事件唯一标识应该是全局唯一的,以便事件能够无歧义地在多个限界上下文中传递。事件基本属性主要记录事件自身以及事件发生背景的数据。

另外事件中还有一项更重要,那就是业务属性,用于记录事件发生那一刻的业务数据,这些数据会随事件传输到订阅方,以开展下一步的业务操作。

事件基本属性和业务属性一起构成事件实体,事件实体依赖聚合根。领域事件发生后,事件中的业务数据不再修改,因此业务数据可以以序列化值对象的形式保存,这种存储格式在消息中间件中也比较容易解析和获取。

为了保证事件结构的统一,我们还会创建事件基类 DomainEvent(参考下图),子类可以扩充属性和方法。由于事件没有太多的业务行为,实现方法一般比较简单。

事件发布之前需要先构建事件实体并持久化。事件发布的方式有很多种,你可以通过应用服务或者领域服务发布到事件总线或者消息中间件,也可以从事件表中利用定时程序或数据库日志捕获技术获取增量事件数据,发布到消息中间件。

  1. 事件数据持久化

事件数据持久化可用于系统之间的数据对账,或者实现发布方和订阅方事件数据的审计。当遇到消息中间件、订阅方系统宕机或者网络中断,在问题解决后仍可继续后续业务流转,保证数据的一致性。

事件数据持久化有两种方案,在实施过程中你可以根据自己的业务场景进行选择。

  • 持久化到本地业务数据库的事件表中,利用本地事务保证业务和事件数据的一致性。
  • 持久化到共享的事件数据库中。这里需要注意的是:业务数据库和事件数据库不在一个数据库中,它们的数据持久化操作会跨数据库,因此需要分布式事务机制来保证业务和事件数据的强一致性,结果就是会对系统性能造成一定的影响。
  1. 事件总线 (EventBus)

事件总线是实现微服务内聚合之间领域事件的重要组件,它提供事件分发和接收等服务。事件总线是进程内模型,它会在微服务内聚合之间遍历订阅者列表,采取同步或异步的模式传递数据。事件分发流程大致如下:

  • 如果是微服务内的订阅者(其它聚合),则直接分发到指定订阅者;
  • 如果是微服务外的订阅者,将事件数据保存到事件库(表)并异步发送到消息中间件;
  • 如果同时存在微服务内和外订阅者,则先分发到内部订阅者,将事件消息保存到事件库(表),再异步发送到消息中间件。

推更及时,拉有延迟,微服务内通过推的方式能尽可能使微服务对外表现得反应更快速;微服务间,需要解耦,不应依赖外部,因此使用拉的方式,而且降低事件发送方的负载。

  1. 消息中间件

跨微服务的领域事件大多会用到消息中间件,实现跨微服务的事件发布和订阅。消息中间件的产品非常成熟,市场上可选的技术也非常多,比如 Kafka,RabbitMQ 等。

  1. 事件接收和处理

微服务订阅方在应用层采用监听机制,接收消息队列中的事件数据,完成事件数据的持久化后,就可以开始进一步的业务处理。领域事件处理可在领域服务中实现。

领域事件运行机制相关案例

这里我用承保业务流程的缴费通知单事件,来给你解释一下领域事件的运行机制。这个领域事件发生在投保和收款微服务之间。发生的领域事件是:缴费通知单已生成。下一步的业务操作是:缴费。

事件起点:出单员生成投保单,核保通过后,发起生成缴费通知单的操作。

  1. 投保微服务应用服务,调用聚合中的领域服务 createPaymentNotice 和 createPaymentNoticeEvent,分别创建缴费通知单、缴费通知单事件。其中缴费通知单事件类 PaymentNoticeEvent 继承基类 DomainEvent。
  2. 利用仓储服务持久化缴费通知单相关的业务和事件数据。为了避免分布式事务,这些业务和事件数据都持久化到本地投保微服务数据库中。
  3. 通过数据库日志捕获技术或者定时程序,从数据库事件表中获取事件增量数据,发布到消息中间件。这里说明:事件发布也可以通过应用服务或者领域服务完成发布。监听MySQL的BinLog,发布后续的消息事件。可以确定一定是落库成功的,解决了分布式事务,但是一定程度上增加了系统复杂性。
  4. 收款微服务在应用层从消息中间件订阅缴费通知单事件消息主题,监听并获取事件数据后,应用服务调用领域层的领域服务将事件数据持久化到本地数据库中。
  5. 收款微服务调用领域层的领域服务 PayPremium,完成缴费。
  6. 事件结束。

提示:缴费完成后,后续流程的微服务还会产生很多新的领域事件,比如缴费已完成、保单已保存等等。这些后续的事件处理基本上跟 1~6 的处理机制类似。

07 | DDD分层架构:有效降低层与层之间的依赖
#

微服务架构模型有好多种,例如整洁架构、CQRS 和六边形架构等等。每种架构模式虽然提出的时代和背景不同,但其核心理念都是为了设计出 高内聚低耦合 的架构,轻松实现架构演进。而 DDD 分层架构的出现,使架构边界变得越来越清晰,它在微服务架构模型中,占有非常重要的位置。

那 DDD 分层架构到底长什么样?DDD 分层架构如何推动架构演进?我们该怎么转向 DDD 分层架构?这就是我们这一讲重点要解决的问题。

什么是 DDD 分层架构

DDD 的分层架构在不断发展。最早是传统的四层架构;后来四层架构有了进一步的优化,实现了各层对基础层的解耦;再后来领域层和应用层之间增加了上下文环境(Context)层,五层架构(DCI)就此形成了。

我们看一下上面这张图,在最早的传统四层架构中,基础层是被其它层依赖的,它位于最核心的位置,那按照分层架构的思想,它应该就是核心,但实际上领域层才是软件的核心,所以这种依赖是有问题的。后来我们采用了依赖倒置(Dependency inversion principle,DIP)的设计,优化了传统的四层架构,实现了各层对基础层的解耦。

我们今天讲的 DDD 分层架构就是优化后的四层架构。在下面这张图中,从上到下依次是:用户接口层、应用层、领域层和基础层。那 DDD 各层的主要职责是什么呢?下面我来逐一介绍一下。

  1. 用户接口层

用户接口层负责向用户显示信息和解释用户指令。这里的用户可能是:用户、程序、自动化测试和批处理脚本等等。

  1. 应用层

应用层是很薄的一层,理论上不应该有业务规则或逻辑,主要面向用例和流程相关的操作。但应用层又位于领域层之上,因为领域层包含多个聚合,所以它可以协调多个聚合的服务和领域对象完成服务编排和组合,协作完成业务操作。

此外,应用层也是微服务之间交互的通道,它可以调用其它微服务的应用服务,完成微服务之间的服务组合和编排。

这里我要提醒你一下:在设计和开发时,不要将本该放在领域层的业务逻辑放到应用层中实现。因为庞大的应用层会使领域模型失焦,时间一长你的微服务就会演化为传统的三层架构,业务逻辑会变得混乱。

逻辑下沉,内聚,如果放应用层,领域层就没有存在的意义了

另外,应用服务是在应用层的,它负责服务的组合、编排和转发,负责处理业务用例的执行顺序以及结果的拼装,以粗粒度的服务通过 API 网关向前端发布。还有,应用服务还可以进行安全认证、权限校验、事务控制、发送或订阅领域事件等。

如果对应spring的约定来看,应用层是service,领域层是repository,repository其实不应该是完全的、直接的映射表的增删改查,而是仅暴露聚合根,内部完成对实体、值对象的操作,但就目前工作而言,常见的都是把repository当成DAO在用了。

  1. 领域层

领域层的作用是实现企业核心业务逻辑,通过各种校验手段保证业务的正确性。领域层主要体现领域模型的业务能力,它用来表达业务概念、业务状态和业务规则。

领域层作用:表达业务概念、业务状态和业务规则;

使用充血模型来实现

领域层包含聚合根、实体、值对象、领域服务等领域模型中的领域对象。

这里我要特别解释一下其中几个领域对象的关系,以便你在设计领域层的时候能更加清楚。首先,领域模型的业务逻辑主要是由实体和领域服务来实现的,其中实体会采用充血模型来实现所有与之相关的业务功能。其次,你要知道,实体和领域服务在实现业务逻辑上不是同级的,当领域中的某些功能,单一实体(或者值对象)不能实现时,领域服务就会出马,它可以组合聚合内的多个实体(或者值对象),实现复杂的业务逻辑。

问题:为什么需要领域服务,既然处理的是聚合内的多个实体间的业务交互,为什么不直接在聚合或聚合根内以充血模型来实现?

  1. 基础层

基础层是贯穿所有层的,它的作用就是为其它各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。比较常见的功能还是提供数据库持久化。

基础层包含基础服务,它采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,降低外部资源变化对应用的影响。

比如说,在传统架构设计中,由于上层应用对数据库的强耦合,很多公司在架构演进中最担忧的可能就是换数据库了,因为一旦更换数据库,就可能需要重写大部分的代码,这对应用来说是致命的。那采用依赖倒置的设计以后,应用层就可以通过解耦来保持独立的核心业务逻辑。当数据库变更时,我们只需要更换数据库基础服务就可以了,这样就将资源变更对应用的影响降到了最低。

这个例子很不好。哪个公司会轻易换数据库?基础层还包括缓存,文件处理等,这些功能使用了依赖倒置(通俗一点说就是使用方和实现方都依赖接口)就可以保证缓存的实现可以是redis可以是local,文件处理可以是ceph可以是ftp等

DDD 分层架构最重要的原则是什么

在《实现领域驱动设计》一书中,DDD 分层架构有一个重要的原则:每层只能与位于其下方的层发生耦合

而架构根据耦合的紧密程度又可以分为两种:严格分层架构和松散分层架构。优化后的 DDD 分层架构模型就属于严格分层架构,任何层只能对位于其直接下方的层产生依赖。而传统的 DDD 分层架构则属于松散分层架构,它允许某层与其任意下方的层发生依赖。

那我们怎么选呢?综合我的经验,为了服务的可管理,我建议你采用严格分层架构。

在严格分层架构中,领域服务只能被应用服务调用,而应用服务只能被用户接口层调用,服务是逐层对外封装或组合的,依赖关系清晰。而在松散分层架构中,领域服务可以同时被应用层或用户接口层调用,服务的依赖关系比较复杂且难管理,甚至容易使核心业务逻辑外泄。

试想下,如果领域层中的某个服务发生了重大变更,那该如何通知所有调用方同步调整和升级呢?但在严格分层架构中,你只需要逐层通知上层服务就可以了。

DDD 分层架构如何推动架构演进

领域模型不是一成不变的,因为业务的变化会影响领域模型,而领域模型的变化则会影响微服务的功能和边界。那我们该如何实现领域模型和微服务的同步演进呢?

  1. 微服务架构的演进

领域模型中对象的层次从内到外依次是:值对象、实体、聚合和限界上下文。

实体或值对象的简单变更,一般不会让领域模型和微服务发生大的变化。但聚合的重组或拆分却可以。这是因为聚合内业务功能内聚,能独立完成特定的业务逻辑。那聚合的重组或拆分,势必就会引起业务模块和系统功能的变化了。

这里我们可以以聚合为基础单元,完成领域模型和微服务架构的演进。聚合可以作为一个整体,在不同的领域模型之间重组或者拆分,或者直接将一个聚合独立为微服务。

我们结合上图,以微服务 1 为例,讲解下微服务架构的演进过程:

  • 当你发现微服务 1 中聚合 a 的功能经常被高频访问,以致拖累整个微服务 1 的性能时,我们可以把聚合 a 的代码,从微服务 1 中剥离出来,独立为微服务 2。这样微服务 2 就可轻松应对高性能场景。
  • 在业务发展到一定程度以后,你会发现微服务 3 的领域模型有了变化,聚合 d 会更适合放到微服务 1 的领域模型中。这时你就可以将聚合 d 的代码整体搬迁到微服务 1 中。如果你在设计时已经定义好了聚合之间的代码边界,这个过程不会太复杂,也不会花太多时间。
  • 最后我们发现,在经历模型和架构演进后,微服务 1 已经从最初包含聚合 a、b、c,演进为包含聚合 b、c、d 的新领域模型和微服务了。

聚合迁移,不止迁代码,聚合中实体的数据也得迁移,这个比较麻烦

你看,好的聚合和代码模型的边界设计,可以让你快速应对业务变化,轻松实现领域模型和微服务架构的演进。你可能还会想,那怎么实现聚合代码快速重组呢?别急,后面实战篇会详细讲解,这里我们先感知下大的实现流程。

  1. 微服务内服务的演进

在微服务内部,实体的方法被领域服务组合和封装,领域服务又被应用服务组合和封装。在服务逐层组合和封装的过程中,你会发现这样一个有趣的现象。

我们看下上面这张图。在服务设计时,你并不一定能完整预测有哪些下层服务会被多少个上层服务组装,因此领域层通常只提供一些原子服务,比如领域服务 a、b、c。但随着系统功能增强和外部接入越来越多,应用服务会不断丰富。有一天你会发现领域服务 b 和 c 同时多次被多个应用服务调用了,执行顺序也基本一致。这时你可以考虑将 b 和 c 合并,再将应用服务中 b、c 的功能下沉到领域层,演进为新的领域服务(b+c)。这样既减少了服务的数量,也减轻了上层服务组合和编排的复杂度。

你看,这就是服务演进的过程,它是随着你的系统发展的,最后你会发现你的领域模型会越来越精炼,越来越能适应需求的快速变化。

三层架构如何演进到 DDD 分层架构

综合前面的讲解,相信 DDD 分层架构的优势,你心里也有个谱了。我们不妨总结一下最最重要两点。

首先,由于层间松耦合,我们可以专注于本层的设计,而不必关心其它层,也不必担心自己的设计会影响其它层。可以说,DDD 成功地降低了层与层之间的依赖。

其次,分层架构使得程序结构变得清晰,升级和维护更加容易。我们修改某层代码时,只要本层的接口参数不变,其它层可以不必修改。即使本层的接口发生变化,也只影响相邻的上层,修改工作量小且错误可以控制,不会带来意外的风险。

那我们该怎样转向 DDD 分层架构呢?不妨看看下面这个过程

传统企业应用大多是单体架构,而单体架构则大多是三层架构。三层架构解决了程序内代码间调用复杂、代码职责不清的问题,但这种分层是逻辑概念,在物理上它是中心化的集中式架构,并不适合分布式微服务架构。

DDD 分层架构中的要素其实和三层架构类似,只是在 DDD 分层架构中,这些要素被重新归类,重新划分了层,确定了层与层之间的交互规则和职责边界。

我们看一下上面这张图,分析一下从三层架构向 DDD 分层架构演进的过程。

首先,你要清楚,三层架构向 DDD 分层架构演进,主要发生在业务逻辑层和数据访问层。

DDD 分层架构在用户接口层引入了 DTO(Data Transfer Object),给前端提供了更多的可使用数据和更高的展示灵活性。

DDD 分层架构对三层架构的业务逻辑层进行了更清晰的划分,改善了三层架构核心业务逻辑混乱,代码改动相互影响大的情况。DDD 分层架构将业务逻辑层的服务拆分到了应用层和领域层。应用层快速响应前端的变化,领域层实现领域模型的能力。

另外一个重要的变化发生在数据访问层和基础层之间。三层架构数据访问采用 DAO 方式;DDD 分层架构的数据库等基础资源访问,采用了仓储(Repository)设计模式,通过依赖倒置实现各层对基础资源的解耦。

仓储又分为两部分:仓储接口和仓储实现。仓储接口放在领域层中,仓储实现放在基础层。原来三层架构通用的第三方工具包、驱动、Common、Utility、Config 等通用的公共的资源类统一放到了基础层。

最后,我想说,传统三层架构向 DDD 分层架构的演进,体现的正是领域驱动设计思想的演进。希望你也感受到了,并尝试将其应用在自己的架构设计中。

08 | 微服务架构模型:几种常见模型的对比和分析
#

今天我们就把 DDD 分层架构、整洁架构、六边形架构这三种架构模型放到一起,对比分析,看看如何利用好它们,帮助我们设计出高内聚低耦合的中台以及微服务架构。

整洁架构

整洁架构又名“洋葱架构”。为什么叫它洋葱架构?看看下面这张图你就明白了。整洁架构的层就像洋葱片一样,它体现了分层的设计思想。

在整洁架构里,同心圆代表应用软件的不同部分,从里到外依次是领域模型、领域服务、应用服务和最外围的容易变化的内容,比如用户界面和基础设施。

整洁架构最主要的原则是依赖原则,它定义了各层的依赖关系,越往里依赖越低,代码级别越高,越是核心能力。外圆代码依赖只能指向内圆,内圆不需要知道外圆的任何情况。

在洋葱架构中,各层的职能是这样划分的:

  • 领域模型实现领域内核心业务逻辑,它封装了企业级的业务规则。领域模型的主体是实体,一个实体可以是一个带方法的对象,也可以是一个数据结构和方法集合。
  • 领域服务实现涉及多个实体的复杂业务逻辑。
  • 应用服务实现与用户操作相关的服务组合与编排,它包含了应用特有的业务流程规则,封装和实现了系统所有用例。
  • 最外层主要提供适配的能力,适配能力分为主动适配和被动适配。主动适配主要实现外部用户、网页、批处理和自动化测试等对内层业务逻辑访问适配。被动适配主要是实现核心业务逻辑对基础资源访问的适配,比如数据库、缓存、文件系统和消息中间件等。
  • 红圈内的领域模型、领域服务和应用服务一起组成软件核心业务能力。

六边形架构

六边形架构又名“端口适配器架构”。追溯微服务架构的渊源,一般都会涉及到六边形架构。

六边形架构的核心理念是:应用是通过端口与外部进行交互的。我想这也是微服务架构下 API 网关盛行的主要原因吧。

也就是说,在下图的六边形架构中,红圈内的核心业务逻辑(应用程序和领域模型)与外部资源(包括 APP、Web 应用以及数据库资源等)完全隔离,仅通过适配器进行交互。它解决了业务逻辑与用户界面的代码交错问题,很好地实现了前后端分离。六边形架构各层的依赖关系与整洁架构一样,都是由外向内依赖。

六边形架构将系统分为内六边形和外六边形两层,这两层的职能划分如下:

  • 红圈内的六边形实现应用的核心业务逻辑;
  • 外六边形完成外部应用、驱动和基础资源等的交互和访问,对前端应用以 API 主动适配的方式提供服务,对基础资源以依赖倒置被动适配的方式实现资源访问。

六边形架构的一个端口可能对应多个外部系统,不同的外部系统也可能会使用不同的适配器,由适配器负责协议转换。这就使得应用程序能够以一致的方式被用户、程序、自动化测试和批处理脚本使用。

三种微服务架构模型的对比和分析

虽然 DDD 分层架构、整洁架构、六边形架构的架构模型表现形式不一样,但你不要被它们的表象所迷惑,这三种架构模型的设计思想正是微服务架构高内聚低耦合原则的完美体现,而它们身上闪耀的正是以领域模型为中心的设计思想。

我们看下上面这张图,结合图示对这三种架构模型做一个分析。

请你重点关注图中的红色线框,它们是非常重要的分界线,这三种架构里面都有,它的作用就是将核心业务逻辑与外部应用、基础资源进行隔离。

基础资源隔离核心思想:依赖抽象,不依赖于具体实现。

红色框内部主要实现核心业务逻辑,但核心业务逻辑也是有差异的,有的业务逻辑属于领域模型的能力,有的则属于面向用户的用例和流程编排能力。按照这种功能的差异,我们在这三种架构中划分了应用层和领域层,来承担不同的业务逻辑。

领域层实现面向领域模型,实现领域模型的核心业务逻辑,属于原子模型,它需要保持领域模型和业务逻辑的稳定,对外提供稳定的细粒度的领域服务,所以它处于架构的核心位置。

应用层实现面向用户操作相关的用例和流程,对外提供粗粒度的 API 服务。它就像一个齿轮一样进行前台应用和领域层的适配,接收前台需求,随时做出响应和调整,尽量避免将前台需求传导到领域层。应用层作为配速齿轮则位于前台应用和领域层之间。

前端需求的多变,领域模型的不变。由内部领域层的原子能力供外部应用层搭积木一样应对前端多变的需求。

可以说,这三种架构都考虑了前端需求的变与领域模型的不变。需求变幻无穷,但变化总是有矩可循的,用户体验、操作习惯、市场环境以及管理流程的变化,往往会导致界面逻辑和流程的多变。但总体来说,不管前端如何变化,在企业没有大的变革的情况下,核心领域逻辑基本不会大变,所以领域模型相对稳定,而用例和流程则会随着外部应用需求而随时调整。把握好这个规律,我们就知道该如何设计应用层和领域层了。

架构模型通过分层的方式来控制需求变化从外到里对系统的影响,从外向里受需求影响逐步减小。面向用户的前端可以快速响应外部需求进行调整和发布,灵活多变,应用层通过服务组合和编排来实现业务流程的快速适配上线,减少传导到领域层的需求,使领域层保持长期稳定。

这样设计的好处很明显了,就是可以保证领域层的核心业务逻辑不会因为外部需求和流程的变动而调整,对于建立前台灵活、中台稳固的架构很有帮助。

看到这里,你是不是已经猜出中台和微服务设计的关键了呢?我给出的答案是:领域模型和微服务的合理分层设计。那么你的答案呢?

从三种架构模型看中台和微服务设计

结合这三种微服务架构模型的共性,下面我来谈谈中台和微服务设计的一些心得体会。

中台本质上是领域的子域,它可能是核心域,也可能是通用域或支撑域。通常大家认为阿里的中台对应 DDD 的通用域,将通用的公共能力沉淀为中台,对外提供通用共享服务。

中台作为子域还可以继续分解为子子域,在子域分解到合适大小,通过事件风暴划分限界上下文以后,就可以定义微服务了,微服务用来实现中台的能力。表面上看,DDD、中台、微服务这三者之间似乎没什么关联,实际上它们的关系是非常紧密的,组合在一起可以作为一个理论体系用于你的中台和微服务设计。

  1. 中台建设要聚焦领域模型

中台需要站在全企业的高度考虑能力的共享和复用。

中台设计时,我们需要建立中台内所有限界上下文的领域模型,DDD 建模过程中会考虑架构演进和功能的重新组合。领域模型建立的过程会对业务和应用进行清晰的逻辑和物理边界(微服务)划分。领域模型的结果会影响到后续的系统模型、架构模型和代码模型,最终影响到微服务的拆分和项目落地。

因此,在中台设计中我们首先要聚焦领域模型,将它放在核心位置。

  1. 微服务要有合理的架构分层

微服务设计要有分层的设计思想,让各层各司其职,建立松耦合的层间关系。

不要把与领域无关的逻辑放在领域层实现,保证领域层的纯洁和领域逻辑的稳定,避免污染领域模型。也不要把领域模型的业务逻辑放在应用层,这样会导致应用层过于庞大,最终领域模型会失焦。如果实在无法避免,我们可以引入防腐层,进行新老系统的适配和转换,过渡期完成后,可以直接将防腐层代码抛弃。

微服务内部的分层方式我们已经清楚了,那微服务之间是否也有层次依赖关系呢?如何实现微服务之间的服务集成?

有的微服务可以与前端应用集成,一起完成特定的业务,这是项目级微服务。而有的则是某个职责单一的中台微服务,企业级的业务流程需要将多个这样的微服务组合起来才能完成,这是企业级中台微服务。两类微服务由于复杂度不一样,集成方式也会有差异。

项目级微服务

项目级微服务的内部遵循分层架构模型就可以了。领域模型的核心逻辑在领域层实现,服务的组合和编排在应用层实现,通过 API 网关为前台应用提供服务,实现前后端分离。但项目级的微服务可能会调用其它微服务,你看在下面这张图中,比如某个项目级微服务 B 调用认证微服务 A,完成登录和权限认证。

通常项目级微服务之间的集成,发生在微服务的应用层,由应用服务调用其它微服务发布在 API 网关上的应用服务。你看下图中微服务 B 中红色框内的应用服务 B,它除了可以组合和编排自己的领域服务外,还可以组合和编排外部微服务的应用服务。它只要将编排后的服务发布到 API 网关供前端调用,这样前端就可以直接访问自己的微服务了。

企业级中台微服务

企业级的业务流程往往是多个中台微服务一起协作完成的,那跨中台的微服务如何实现集成呢?

企业级中台微服务的集成不能像项目级微服务一样,在某一个微服务内完成跨微服务的服务组合和编排。

我们可以在中台微服务之上增加一层,你看下面这张图,增加的这一层就位于红色框内,它的主要职能就是处理跨中台微服务的服务组合和编排,以及微服务之间的协调,它还可以完成前端不同渠道应用的适配。如果再将它的业务范围扩大一些,我可以将它做成一个面向不同行业和渠道的服务平台。

我们不妨借用 BFF(服务于前端的后端,Backend for Frontends)这个词,暂且称它为 BFF 微服务。BFF 微服务与其它微服务存在较大的差异,就是它没有领域模型,因此这个微服务内也不会有领域层。BFF 微服务可以承担应用层和用户接口层的主要职能,完成各个中台微服务的服务组合和编排,可以适配不同前端和渠道的要求。

  1. 应用和资源的解耦与适配

传统以数据为中心的设计模式,应用会对数据库、缓存、文件系统等基础资源产生严重依赖。

正是由于它们之间的这种强依赖的关系,我们一旦更换基础资源就会对应用产生很大的影响,因此需要为应用和资源解耦。

在微服务架构中,应用层、领域层和基础层解耦是通过仓储模式,采用依赖倒置的设计方法来实现的。在应用设计中,我们会同步考虑和基础资源的代码适配,那么一旦基础设施资源出现变更(比如换数据库),就可以屏蔽资源变更对业务代码的影响,切断业务逻辑对基础资源的依赖,最终降低资源变更对应用的影响。

未来肯定会出现当前技术选型选定的基础设施层和辅助工具的替代者,如果能非常方便快捷,而且风险可控的情况下能够替换掉这些与业务无关的组件,这样才可以源源不断地吃到新技术带来的红利。

09 | 中台:数字转型后到底应该共享什么?
#

中台是数字化转型的一个热门话题。继阿里提出中台概念后,很多人又提出了各种各样的中台。今天我们主要讨论业务中台和数据中台。作为企业数字化中台转型的整体,我也会顺带聊一聊前台和后台的一些设计思路。

不少企业其实在很多年前就有了建大平台的实践经验,那在中台被热议时,我相信你一定听过很多质疑声。比如,有人说:“中台就是个怪名词,它不就是已经做了好多年的平台吗?”确实,中台源于平台,但它的战略高度要比平台高很多

学完这一讲,你就会清楚地知道平台与中台的差异在什么地方?中台到底是什么?传统企业的中台建设方式是否应该和阿里一样…

平台到底是不是中台

阿里提出中台战略后,很多企业开始拿着自己的系统与阿里的中台对标。有的企业在十多年前就完成了大一统的集中式系统拆分,实现了从传统大单体应用向大平台的演进,他们将公共能力和核心能力分开建设,解决了公共模块重复投入和重复建设的问题。

那这是不是阿里所说的中台呢?在回答这个问题之前,我们不妨先了解一下阿里的中台到底是什么样的。

阿里业务中台的前身是共享平台,而原来的共享平台更多的被当作资源团队,他们承接各业务方的需求,并为业务方在基础服务上做定制开发。 阿里业务中台的目标是把核心服务链路(会员、商品、交易、营销、店铺、资金结算等)整体当作一个平台产品来做,为前端业务提供的是业务解决方案,而不是彼此独立的系统。

下面我们分析一下传统企业大平台战略和阿里中台战略的差异。

平台只是将部分通用的公共能力独立为共享平台。虽然可以通过 API 或者数据对外提供公共共享服务,解决系统重复建设的问题,但这类平台并没有和企业内的其它平台或应用,实现页面、业务流程和数据从前端到后端的全面融合,并且没有将核心业务服务链路作为一个整体方案考虑,各平台仍然是分离且独立的。

中台的观察视角和战略比平台高一些,是企业架构或者企业信息化整体战略的结果,阿里当时面临着天猫体系和淘宝体系以及聚划算等等多个相对独立的业务部门,各个业务部门的系统共享比较差,于是通过打造中台来将各个业务线的通用业务逻辑和通用系统抽取出来,逐步沉淀和优化,以至于后期各种新的业务应用可以快速的基于这套中台系统实现出来,形成了大中台小前台的状态,所以中台应该是一套通用业务系统的集合体。但平台没有与企业内其它平台融合,没有形成一个整体的解决方案,各平台仍然互相独立。

平台解决了公共能力复用的问题,但离中台的目标显然还有一段差距!

中台到底是什么

“一千个读者就有一千个哈姆雷特”,这句话形容技术圈对中台的定义再合适不过了,说法很多。

先看一下阿里自己人对中台的定义:“中台是一个基础的理念和架构,我们要把所有的基础服务用中台的思路建设,进行联通,共同支持上端的业务。业务中台更多的是支持在线业务,数据中台提供了基础数据处理能力和很多的数据产品给所有业务方去用。业务中台、数据中台、算法中台等等一起提供对上层业务的支撑。”

再看一下思特沃克对中台的定义:“中台是企业级能力复用平台。”

综上,我们可以提炼出几个关于中台的关键词:共享、联通、融合和创新。联通是前台以及中台之间的联通,融合是前台流程和数据的融合,并以共享的方式支持前端一线业务的发展和创新。

我认为,中台首先体现的是一种企业级的能力,它提供的是一套企业级的整体解决方案,解决小到企业、集团,大到生态圈的能力共享、联通和融合问题,支持业务和商业模式创新。通过平台联通和数据融合为用户提供一致的体验,更敏捷地支撑前台一线业务。

中台来源于平台,但中台和平台相比,它更多体现的是一种理念的转变,它主要体现在这三个关键能力上:对前台业务的快速响应能力;企业级复用能力;从前台、中台到后台的设计、研发、页面操作、流程服务和数据的无缝联通、融合能力。

数据互通和融合才是中台最大的特点,单纯的提供平台服务无法解决数据孤岛问题,从公司层面看,能力还是分散成一块一块的。

其中最关键的是快速响应能力和企业级的无缝联通和融合能力,尤其是对于跨业经营的超大型企业来说至关重要。

数字化转型中台应该共享什么

相对互联网企业而言,传统企业的渠道应用更多样化,有面向内部人员的门店类应用、面向外部用户的互联网电商以及移动 APP 类应用。这些应用面向的用户和场景可能不同,但其功能类似,基本涵盖了核心业务能力。此外,传统企业也会将部分核心应用的页面或 API 服务能力开放给生态圈第三方,相互借力发展。

为了适应不同业务和渠道的发展,过去很多企业的做法是开发很多独立的应用或 APP。但由于 IT 系统建设初期并没有企业级的整体规划,平台之间融合不好,就导致了用户体验不好,最关键的是用户并不想装那么多 APP。

为了提升用户体验,实现统一运营,很多企业开始缩减 APP 的数量,开始通过一个 APP 集成企业内的所有能力,联通前台所有的核心业务链路。

由于传统企业的商业模式和 IT 系统建设发展的历程与互联网企业不是完全一样的,因此传统企业的中台建设策略与阿里中台战略也应该有所差异,需要共享的内容也不一样。

由于渠道多样化,传统企业不仅要将通用能力中台化,以实现通用能力的沉淀、共享和复用,这里的通用能力对应 DDD 的通用域或支撑域;传统企业还需要将核心能力中台化,以满足不同渠道的核心业务能力共享和复用的需求,避免传统核心和互联网不同渠道应用出现“后端双核心、前端两张皮”的问题,这里的核心能力对应 DDD 的核心域。

这就属于业务中台的范畴了,我们需要解决核心业务链路的联通和不同渠道服务共享的问题。除此之外,我们还需要解决系统微服务拆分后的数据孤岛、数据融合和业务创新等问题,这就属于数据中台的范畴了,尤其是当我们采用分布式架构以后,我们就更应该关注微服务拆分后的数据融合和共享问题了。

综上,在中台设计和规划时,我们需要整体考虑企业内前台、中台以及后台应用的协同,实现不同渠道应用的前端页面、流程和服务的共享,还有核心业务链路的联通以及前台流程和数据的融合、共享,支持业务和商业模式的创新。

如何实现前中后台的协同

企业级能力往往是前中后台协同作战能力的体现。

如果把业务中台比作陆军、火箭军和空军等专业军种的话,它主要发挥战术专业能力。前台就是作战部队,它需要根据前线的战场需求,对业务中台的能力进行调度,实现能力融合和效率最大化。而数据中台就是信息情报中心和联合作战总指挥部,它能够汇集各种数据、完成分析,制定战略和战术计划。后台就是后勤部队,提供技术支持。下面我们分别来说说。

  1. 前台

传统企业的早期系统有不少是基于业务领域或组织架构来建设的,每个系统都有自己的前端,相互独立,用户操作是竖井式,需要登录多个系统才能完成完整的业务流程。

中台后的前台建设要有一套综合考虑业务边界、流程和平台的整体解决方案,以实现各不同中台前端操作、流程和界面的联通、融合。不管后端有多少个中台,前端用户感受到的就是只有一个前台

在前台设计中我们可以借鉴微前端的设计思想,在企业内不仅实现前端解耦和复用,还可以根据核心链路和业务流程,通过对微前端页面的动态组合和流程编排,实现前台业务的融合。

前端页面可以很自然地融合到不同的终端和渠道应用核心业务链路中,实现前端页面、流程和功能复用。

  1. 中台

传统企业的核心业务大多是基于集中式架构开发的,而单体系统存在扩展性和弹性伸缩能力差的问题,因此无法适应忽高忽低的互联网业务场景。而数据类应用也多数通过 ETL 工具抽取数据实现数据建模、统计和报表分析功能,但由于数据时效和融合能力不够,再加上传统数据类应用本来就不是为前端而生的,因此难以快速响应前端一线业务。

业务中台的建设可采用领域驱动设计方法,通过领域建模,将可复用的公共能力从各个单体剥离,沉淀并组合,采用微服务架构模式,建设成为可共享的通用能力中台。

同样的,我们可以将核心能力用微服务架构模式,建设成为可面向不同渠道和场景的可复用的核心能力中台。 业务中台向前台、第三方和其它中台提供 API 服务,实现通用能力和核心能力的复用。

但你需要记住这一点:在将传统集中式单体按业务职责和能力细分为微服务,建设中台的过程中,会产生越来越多的独立部署的微服务。这样做虽然提升了应用弹性和高可用能力,但由于微服务的物理隔离,原来一些系统内的调用会变成跨微服务调用,再加上前后端分离,微服务拆分会导致数据进一步分离,增加企业级应用集成的难度。

如果没有合适的设计和指导思想,处理不好前台、中台和后台的关系,将会进一步加剧前台流程和数据的孤岛化、碎片化。

数据中台的主要目标是打通数据孤岛,实现业务融合和创新,包括三大主要职能:

  • 一是完成企业全域数据的采集与存储,实现各不同业务类别中台数据的汇总和集中管理。
  • 二是按照标准的数据规范或数据模型,将数据按照不同主题域或场景进行加工和处理,形成面向不同主题和场景的数据应用,比如客户视图、代理人视图、渠道视图、机构视图等不同数据体系。
  • 三是建立业务需求驱动的数据体系,基于各个维度的数据,深度萃取数据价值,支持业务和商业模式的创新。

相应的,数据中台的建设就可分为三步走:

  • 第一步实现各中台业务数据的汇集,解决数据孤岛和初级数据共享问题。
  • 第二步实现企业级实时或非实时全维度数据的深度融合、加工和共享。
  • 第三步萃取数据价值,支持业务创新,加速从数据转换为业务价值的过程。

数据中台不仅限于分析型场景,也适用于交易型场景。它可以建立在数据仓库或数据平台之上,将数据服务化之后提供给业务系统。基于数据库日志捕获的技术,使数据的时效性大大提升,这样就可以为交易型场景提供很好的支撑。

综上,数据中台主要完成数据的融合和加工,萃取数据业务价值,支持业务创新,对外提供数据共享服务。

  1. 后台

很多人提到中台时自然会问:“既然有前台和中台,那是否有后台,后台的职责又是什么?”

我们来看一下阿里对前台、中台和后台的定位。

前台主要面向客户以及终端销售者,实现营销推广以及交易转化;中台主要面向运营人员,完成运营支撑;后台主要面向后台管理人员,实现流程审核、内部管理以及后勤支撑,比如采购、人力、财务和 OA 等系统。

那对于后台,为了实现内部的管理要求,很多人习惯性将这些管理要求嵌入到核心业务流程中。而一般来说这类内控管理需求对权限、管控规则和流程等要求都比较高,但是大部分管理人员只是参与了某个局部业务环节的审核。这类复杂的管理需求,会凭空增加不同渠道应用前台界面和核心流程的融合难度以及软件开发的复杂度。

在设计流程审核和管理类功能的时候,我们可以考虑按角色或岗位进行功能聚合,将复杂的管理需求从通用的核心业务链路中剥离,参考小程序的建设模式,通过特定程序入口嵌入前台 APP 或应用中。

管理需求从前台核心业务链路剥离后,前台应用将具有更好的通用性,它可以更加容易地实现各渠道前台界面和流程的融合。一个前台应用或 APP 可以无差别地同时面向外部互联网用户和内部业务人员,从而促进传统渠道与互联网渠道应用前台的融合。

10 | DDD、中台和微服务:它们是如何协作的?
#

DDD 和微服务来源于西方,而中台诞生于中国的阿里巴巴。DDD 在二十多年前提出后一直默默前行,中台和微服务的理念近几年才出现,提出后就非常火爆。这三者看似风马牛不相及,实则缘分匪浅。中台是抽象出来的业务模型,微服务是业务模型的系统实现,DDD 作为方法论可以同时指导中台业务建模和微服务建设,三者相辅相成,完美结合。

中台和更强调业务视角,微服务是技术实现,DDD是建设方法论。

你可能会问:凭什么 DDD 可以指导中台和微服务建设,究竟起到了什么作用呢?

DDD 有两把利器,那就是它的战略设计和战术设计方法

中台在企业架构上更多偏向业务模型,形成中台的过程实际上也是业务领域不断细分的过程。在这个过程中我们会将同类通用的业务能力进行聚合和业务重构,再根据限界上下文和业务内聚的原则建立领域模型。而 DDD 的战略设计最擅长的就是领域建模。

那在中台完成领域建模后,我们就需要通过微服务来完成系统建设。此时,DDD 的战术设计又恰好可以与微服务的设计完美结合。可以说,中台和微服务正是 DDD 实战的最佳场景。

DDD 的本质

我们先简单回顾一下 DDD 领域、子域、核心域、通用域和支撑域等概念,后面会用到。

在研究和解决业务问题时,DDD 会按照一定的规则将业务领域进行细分,领域细分到一定的程度后,DDD 会将问题范围限定在特定的边界内,并在这个边界内建立领域模型,进而用代码实现该领域模型,解决相应的业务问题。领域可分解为子域,子域可继续分为子子域,一直到你认为适合建立领域模型为止。

子域还会根据自身重要性和功能属性划分为三类子域,它们分别是核心域、支撑域和通用域。

接下来我们一起看下上面这张图,我选择了保险的几个重要领域,进行了高阶的领域划分。当然每个企业的领域定位和职责会有些不一样,那在核心域的划分上肯定会有一定差异。因此,当你去做领域划分的时候,请务必结合企业战略,这恰恰也体现了 DDD 领域建模的重要性。

通过领域划分和进一步的子域划分,我们就可以区分不同子域在企业内的功能属性和重要性,进而采取不同的资源投入和建设策略,这在企业 IT 系统的建设过程中十分重要,并且这样的划分还可以帮助企业进行中台设计。

中台的本质

中台来源于阿里的中台战略(详见《企业 IT 架构转型之道:阿里巴巴中台战略思想与架构实战》钟华编著)。2015 年年底,阿里巴巴集团对外宣布全面启动中台战略,构建符合数字时代的更具创新性、灵活性的“大中台、小前台”组织机制和业务机制,即作为前台的一线业务会更敏捷、更快速地适应瞬息万变的市场,而中台将集合整个集团的运营数据能力、产品技术能力,对各前台业务形成强力支撑。

中台的本质其实就是提炼各个业务板块的共同需求,进行业务和系统抽象,形成通用的可复用的业务模型,打造成组件化产品,供前台部门使用。前台要做什么业务,需要什么资源,可以直接找中台,不需要每次都去改动自己的底层。

DDD、中台和微服务的协作模式

传统企业和阿里中台战略的差异,那实际上更多的企业还是会聚焦在传统企业中台建设的模式,也就是将通用能力与核心能力全部中台化,以满足不同渠道核心业务能力的复用,那么接下来我们就还是把重点放在传统企业上。

传统企业可以将需要共享的公共能力进行领域建模,建设可共享的通用中台。除此之外,传统企业还会将核心能力进行领域建模,建设面向不同渠道的可复用的核心中台。

而这里的通用中台和核心中台都属于我们上一讲讲到的业务中台的范畴。

DDD 的子域分为核心域、通用域和支撑域。划分这几个子域的主要目的是为了确定战略资源的投入,一般来说战略投入的重点是核心域,因此后面我们就可以暂时不严格区分支撑域和通用域了。

领域、中台以及微服务虽然属于不同层面的东西,但我们还是可以将他们分解对照,整理出来它们之间的关系。你看下面这张图,我是从 DDD 领域建模和中台建设这两个不同的视角对同一个企业的业务架构进行分析。

如果将企业内整个业务域作为一个问题域的话,企业内的所有业务就是一个领域。在进行领域细分时,从 DDD 视角来看,子域可分为核心域、通用域和支撑域。从中台建设的视角来看,业务域细分后的业务中台,可分为核心中台和通用中台。

从领域功能属性和重要性对照来看,通用中台对应 DDD 的通用域和支撑域,核心中台对应 DDD 的核心域。从领域的功能范围来看,子域与中台是一致的。领域模型所在的限界上下文对应微服务。建立了这个映射关系,我们就可以用 DDD 来进行中台业务建模了。

我们这里还是以保险领域为例。保险域的业务中台分为两类:第一类是提供保险核心业务能力的核心中台(比如营销、承保和理赔等业务);第二类是支撑核心业务流程完成保险全流程的通用中台(比如订单、支付、客户和用户等)。

这里我要提醒你一下:根据 DDD 首先要建立通用语言的原则,在将 DDD 的方法引入中台设计时,我们要先建立中台和 DDD 的通用语言。这里的子域与中台是一致的,那我们就可以将子域统一为中台。

中台通过事件风暴可以进一步细分,最终完成业务领域建模。中台业务领域的功能不同,限界上下文的数量和大小就会不一样,领域模型也会不一样。

当完成业务建模后,我们就可以采用 DDD 战术设计,设计出聚合、实体、领域事件、领域服务以及应用服务等领域对象,再利用分层架构模型完成微服务的设计。

以上就是 DDD、中台和微服务在应用过程中的协作模式。

中台如何建模

看完了三者的协作模式,我们就顺着上面的话题,接着来聊聊中台如何建模。

中台业务抽象的过程就是业务建模的过程,对应 DDD 的战略设计。系统抽象的过程就是微服务的建设过程,对应 DDD 的战术设计。下面我们就结合 DDD 领域建模的方法,讲一下中台业务建模的过程。

  1. 按照业务流程(通常适用于核心域)或者功能属性、集合(通常适用于通用域或支撑域),将业务域细分为多个中台,再根据功能属性或重要性归类到核心中台或通用中台。核心中台设计时要考虑核心竞争力,通用中台要站在企业高度考虑共享和复用能力。
  2. 选取中台,根据用例、业务场景或用户旅程完成事件风暴,找出实体、聚合和限界上下文。依次进行领域分解,建立领域模型。
    • 由于不同中台独立建模,某些领域对象或功能可能会重复出现在其它领域模型中,也有可能本该是同一个聚合的领域对象或功能,却分散在其它的中台里,这样会导致领域模型不完整或者业务不内聚。这里先不要着急,这一步我们只需要初步确定主领域模型就可以了,在第三步中我们还会提炼并重组这些领域对象。
  3. 以主领域模型为基础,扫描其它中台领域模型,检查并确定是否存在重复或者需要重组的领域对象、功能,提炼并重构主领域模型,完成最终的领域模型设计。
  4. 选择其它主领域模型重复第三步,直到所有主领域模型完成比对和重构。
  5. 基于领域模型完成微服务设计,完成系统落地。

结合上面这张图,你可以大致了解到 DDD 中台设计的过程。DDD 战略设计包括上述的第一步到第四步,主要为:业务域分解为中台,对中台归类,完成领域建模,建立中台业务模型。DDD 战术设计是第五步,领域模型映射为微服务,完成中台建设。

那么如果还是以保险领域为例的话,完成领域建模后,里面的数据我们就可以填上了。这里我选取了通用中台的用户、客户和订单三个中台来做示例。客户中台提炼出了两个领域模型:客户信息和客户视图模型。用户中台提炼出了三个领域模型:用户管理、登录认证和权限模型。订单中台提炼出了订单模型。

这就是中台建模的全流程,当然看似简单的背后,若是遇上复杂的业务总会出现各种各样的问题,不然应用起来也不会有那么多的困难。如果你在按照以上流程实施的过程中遇到什么问题,欢迎在留言区和我讨论。

有关3个典型问题的讲解
#

问题 1:有关于领域可以划分为核心域、通用域和支撑域,以及子域和限界上下文关系的话题,还有是否有边界划分的量化标准?

在领域不断划分的过程中,领域会被细分为不同的子域,这个过程实际上是将问题范围不断缩小的过程。

对于领域问题来说,可以理解为,对一个问题不断地划分,直到划分为我们熟悉的、能够快速处理的小问题。然后再对小问题的处理排列一个优先级。

这个理解是很到位的。在领域细分到一定的范围后,我们就可以对这个子域进行事件风暴,为这个子域划分限界上下文,建立领域模型,然后就可以基于领域模型进行微服务设计了。

虽然 DDD 没有明确说明子域和限界上下文的关系。我个人认为,子域的划分是一种比较粗的领域边界的划分,它不考虑子域内的领域对象、对象之间的关系和结构。子域的划分往往按照业务阶段或者功能模块边界进行粗分,其目的就是为了让你能够在一个相对较小的问题空间内,比较方便地用事件风暴来梳理业务场景。

而限界上下文本质上也是子域,限界上下文是在明确的子域内,用事件风暴划分出来的。它体现的是一种详细的设计过程。这个过程设计出了领域模型,明确了领域对象以及领域对象的依赖等关系,有了领域模型,你就可以直接进行微服务设计了。

关于核心域、通用域和支撑域,划分这三个不同类型子域的主要目的是为了区分业务域的优先级,确定 IT 战略投入。我们会将重要的资源投入在核心域上,确保好钢用在刀刃上。每个企业由于商业模式或者战略方向不一样,核心域会有一些差异,不要用固定的眼光看待不同企业的核心域。

核心域、通用域和支撑域都是业务领域,只不过重要性和功能属性不一样。采用的 DDD 设计方法和过程,是没有差异的。

从目前来看,还没有可以量化的领域以及限界上下文的划分标准。它主要依赖领域专家经验,以及和项目团队在事件风暴过程中不断地权衡和分析。不要奢望一次迭代就能够给复杂的业务,建立一个完美的领域模型。领域模型很多时候也需要多次迭代才能成型,它也需要不断地演进。但如果是用 DDD 设计出来的领域模型的边界和微服务内聚合的边界非常清晰的话,这个演进过程相对来说会简单很多,所需的时间成本也会很低。

问题 2:关于聚合设计的问题?领域层与基础层为什么要依赖倒置(DIP)?

聚合主要实现核心业务逻辑,里面有很多的领域对象,这些领域对象之间需要通过聚合根进行统一的管理,以确保数据的一致性。

在聚合设计时,我们会用到两个重要的设计模式:工厂(Factory)模式和仓储(Repository)模式。如果你有兴趣详细了解的话,推荐你阅读《实现领域驱动设计》一书的第 11 章和第 12 章。

那为什么要引入工厂模式呢?

这是因为有些聚合内可能含有非常多的实体和值对象,我们需要确保聚合根以及所有被依赖的对象实例同时被创建。如果都通过聚合根来构造,将会非常复杂。因此我们可以通过工厂模式来封装复杂对象的创建过程,但并不是所有对象的构造都需要用到工厂,如果构造过程不复杂,只是单一对象的构造,你用简单的构造方法就足够了。

又为什么要引入仓储模式?解答这个问题的同时,我也一起将依赖倒置的问题解答一下。

在传统的 DDD 四层架构中,所有层都是依赖基础层的。这样做有什么不好的地方呢?如果应用逻辑对基础层依赖太大的话,基础层中与资源有关的代码可能会渗透到应用逻辑中。而现在技术组件的更新频率是很快的,一旦出现基础组件的变更,且基础组件的代码被带入到了应用逻辑中,这样会对上层的应用逻辑产生致命的影响。

为了解耦应用逻辑和基础资源,在基础层和上层应用逻辑之间会增加一层,这一层就是仓储层。一个聚合对应一个仓储,仓储实现聚合内数据的持久化。聚合内的应用逻辑通过接口来访问基础资源,仓储实现在基础层实现。这样应用逻辑和基础资源的实现逻辑是分离的。如果变更基础资源组件,只需要替换仓储实现就可以了,不会对应用逻辑产生太大的影响,这样就实现了应用逻辑与基础资源的解耦,也就实现了依赖倒置。

关于聚合设计过程中的一些原则问题。大部分的业务场景我们都可以通过事件风暴,找到聚合根,建立聚合,划分限界上下文,建立领域模型。但也有部分场景,比如数据计算、统计以及批处理业务场景,所有的实体都是独立无关联的,找不到聚合根,也无法建立领域模型。但是它们之间的业务关系是非常紧密的,在业务上是高内聚的。我们也可以将这类场景作为一个聚合处理,除了不考虑聚合根的设计方法外,其它诸如 DDD 分层架构相关的设计方法都是可以采用的。

一些业务场景,如果复杂度并不高,而用 DDD 设计会带来不必要的麻烦的话,比如增加复杂度,有些原则也是可以突破的,不要为做 DDD 而做 DDD。即使采用传统的方式也是没有关系的,最终以解决实际问题为最佳。但必须记住一点,如果采用传统的设计方式,一定要保证领域模型的边界以及微服务内聚合的逻辑边界清晰,这样的话,以后微服务的演进就不会太复杂。

问题 3:领域事件采用消息异步机制,发布方和订阅方数据如何保证一致性?微服务内聚合之间领域事件是否一定要用事件总线?

在领域事件设计中,为了解耦微服务,微服务之间数据采用最终一致性原则。由于发布方是在消息总线发布消息以后,并不关心数据是否送达,或者送达后订阅方是否正常处理,因此有些技术人会担心发布方和订阅方数据一致性的问题。

那在对数据一致性要求比较高的业务场景,我们是有相关的设计考虑的。也就是发送方和订阅方的事件数据都必须落库,发送方除了保存业务数据以外,在往消息中间件发布消息之前,会先将要发布的消息写入本地库。而接收方在处理消息之前,需要先将收到的消息写入本地库。然后可以采用定期对发布方和订阅方的事件数据对账的操作,识别出不一致的数据。如果数据出现异常或不一致的情况,可以启动定时程序再次发送,必要时可以转人工操作处理。

其实 有2个办法 第一 做链路追踪服务 可以直观的看到 消息卡在哪一步 第二 消息发送接收 第一步都是落库 并对消息进行处理监控 处理失败 或者发送失败 根据配置化 判断重试次数和时间

关于事件总线的问题。由于微服务内的逻辑都在一个进程内,后端数据库也是一个,微服务内的事务相比微服务之间会好控制一些。在处理微服务内的领域事件时,如果引入事件总线,会增加开发的复杂度,那是否引入事件总线,就需要你来权衡。

个人感觉如果你的场景中,不会出现导致聚合之间数据不一致的情况,就可以不使用事件总线。另外,通过应用服务也可以实现聚合之间的服务和数据协调。


💬评论