我在《解构领域驱动设计》一书中定义了领域驱动设计统一过程,将整个领域驱动设计过程分为了三个阶段:
- 全局分析阶段
- 架构映射阶段
- 领域建模阶段
我还在书中的附录D给出了领域驱动设计统一过程的交付物。不过,随着我在多个项目中实践领域驱动设计统一过程,我发现定义在交付物的一些关键元素需要更好的表现方式,也需要形成一个统一的标准。为了帮助各位《解构领域驱动设计》的读者在项目中更好地运用领域驱动设计统一过程,并输出高质量的统一而标准的交付物,特别作此文以补充。为保证交付物的完整性,个别未做更新和改进的内容仍然在本文中有所呈现,各位读者可以自行对比文中和书中的内容。
01全局分析阶段
1业务流程
梳理问题空间的业务需求,获得用泳道图表现的业务流程:
2业务服务
根据业务服务的定义分析业务流程,识别出业务服务,并以业务服务图(参考用例图)形式表示:
说明:如果采用敏捷方式管理需求,可以将业务服务作为用户故事的子任务,它不包括前端的交互设计和开发内容。
如果需要进一步细化业务服务,则需要按照如下格式编写业务服务规约:
编写业务服务规约时,需要遵循统一语言。
以上内容,可以构成目标系统的需求规格说明书。
02架构映射阶段
1系统上下文
系统上下文用于呈现目标系统的系统边界,明确目标系统与角色、伴生系统之间的关系。可以通过改进的系统上下文图来表示:
改进的系统上下文有效地利用了四个方位:
- 上方:代表北向,即调用目标系统的伴生系统或模块
- 下方:代表南向,即目标系统调用的伴生系统或模块
- 左方:代表使用目标系统的所有角色
- 右方:代表互相调用的伴生系统或模块
2限界上下文
识别限界上下文
运用服务风暴法,识别限界上下文,建立业务服务与限界上下文的映射关系,并以下图形式呈现出来:
图中的菱形代表限界上下文,椭圆形代表业务服务。
确定上下文映射
针对每一个业务服务,通过业务服务规约绘制服务序列图,以确定限界上下文之间的协作关系,并驱动出每个限界上下文的服务契约。绘制服务序列图时,根据业务服务规约“成功场景”部分的流程,确定每个流程步骤需要的领域知识和领域职责应该由哪一个限界上下文负责。服务序列图如下所示:
通过服务序列图,既可以明确限界上下文之间的关系,又可以驱动出每个限界上下文包括伴生系统的服务契约(API),同时还能够确定协作模式,包括客户方-供应方模式和发布者-订阅者模式。其中,查询和命令方式属于客户方-供应方模式,事件方式属于发布者-订阅者模式。服务契约可以通过下表格式表示:
服务契约的API定义也可以在Swagger中维护。
最后,可以通过如下图示表示限界上下文:
与改进的系统上下文图相似,限界上下文图也有效地利用了四个方位:
- 上方:代表北向服务,为当前限界上下文对外公开的服务接口
- 下方:代表南向服务,为当前限界上下文调用上游限界上下文或伴生系统的服务接口
- 左方:当前限界上下文订阅的事件
- 右方:当前限界上下文发布的事件
限界上下文内部可以呈现属于当前限界上下文领域模型的聚合,如果还未开展领域建模,可以为空。
菱形对称架构
限界上下文的内部应遵循如下图所示的菱形对称架构:
菱形对称架构的核心思想:
- 内外分离:内部的领域层与外部的网关层分离,保证业务和技术的正交性
- 南北对称:南向网关采用抽象思想,隔离外部资源变化对内部领域层带来的影响;北向网关采用封装思想,通过定义远程服务和本地服务隔离内部领域逻辑对外部调用者的影响
系统分层架构
在目标系统层面上,需要将各个限界上下文组织在如下图所示的系统分层架构中:
代码模型
遵循菱形对称架构,一个完整的代码模型如下所示:
● valueaddedlayer
○ boundedcontext
■ north
● remote
○ resource
○ controller
○ provider
○ subscriber
● local
● message
■ domain
● aggregate
○ entity
○ valueobject
○ domainservice
■ south
● port
○ repository
○ client
○ publisher
● adapter
○ repository
○ client
○ publisher
以上内容构成了目标系统的架构设计文档。
03领域建模阶段
1领域分析建模
领域建模阶段是通过对业务服务规约进行领域分析建模开始的。领域分析建模与具体的建模技术和设计方法没有任何关系,只是从业务的角度通过提取领域概念获得最终的领域分析模型。该方法为快速建模法,得到的模型如下图所示:
图中的灰色领域概念是通过动词建模法获得的。整个领域分析模型需要分配给对应的限界上下文。
2领域设计建模
静态设计模型
领域设计建模从下图所示的领域分析模型开始:
识别实体和值对象:
确定实体之间的关系:
根据实体关系的强弱划定聚合的边界,获得以聚合为中心的领域设计模型:
动态设计模型
获得动态设计模型的过程如下图所示:
分析业务服务,获得如下所示的业务服务规约:
服务编号:033
服务名:报名活动
服务描述:
作为报名人
我想要报名活动
以便于预留活动报名资格
触发事件:
报名人选择自己想要报名的活动,点击“报名”按钮
基本流程
1. 检查报名人是否有效
2. 检查报名通道是否已关闭
3. 检查该报名人是否已报名
4. 完成报名预约
5. 发送报名预约成功的通知
替换流程
1.a 若报名人无效,给出提示信息
2.a 如果报名通道已关闭,给出提示信息
3.a 如果已报名,给出提示信息
4.a 如报名失败,给出失败原因
验收标准:
1. 报名人必须是活动所属部落的会员
2. 报名达到截止日期或者报名人数已到达上限,则视为报名通道已关闭
3. 报名人不能重复报名
4. 完成报名后,报名状态设置为“已预订”
5. 报名人接收到预约成功的通知
根据业务服务规约获得如下所示的任务树:
● 报名活动
○ 验证报名
■ 验证报名人是否会员 --- 访问部落上下文
■ 确定报名通道是否已关闭
● 获取报名通道
● 确定是否已关闭
■ 验证报名单 ---- Repository
○ 生成报名单 --- Repository
○ 更新报名通道
■ 加载报名通道
■ 更新
■ 保存报名通道
● 发送报名预约成功的通知 --- 通知上下文
分配职责给对应的角色构造型,形成序列图脚本:
TicketController.enrollActivity(EnrollingRequest) { TicketAppService.enrollActivity(EnrollingRequest) { Ticket ticket = EnrollingRequest.to() TicketService.enrollActivity(ticket) { TicketService.validate(ticket) { MemberClient.isMember(enrollerId, tribeId) EnrollingChannelService.isClosed(activityId) { EnrollingChannel channel = EnrollingChannelRepository.channelOf(activityId) channel.isClosed() } TicketRepository.isExists(enrollerId, activityId, TicketStatus) } TicketRepository.add(ticket) EnrollingChannelService.occupiedBy(activityId) { EnrollingChannel channel = EnrollingChannelRepository.channelOf(activityId) channel.occupiedWith(1) EnrollingChannelRepository.save(channel) } } ActivitySubscribedPublisher.publish(ActivitySubscribed) } }
领域建模阶段输出的静态领域设计模型与动态领域设计模型共同组成限界上下文的设计文档。