在《解构领域驱动设计》书中的领域建模阶段,我提出了以业务服务为核心进行设计与建模的方法——服务驱动设计。通过该方法可以在静态的领域设计模型基础之上,以业务服务规约为基础,通过分析需求,对业务服务进行任务分解,获得以子任务构成的任务树。这棵树以业务服务为根,组合任务为枝,原子任务为叶,既体现了业务服务的执行过程,又进行了适度的封装,建立了一定的封装层次。
一旦获得了子任务树,即可对树中的每一个子任务进行职责分配,根据其特点分别分配给远程服务、本地服务、领域服务、聚合、端口。它们是构成限界上下文的主要对象角色,我将其称之为“角色构造型”,可以和我提出的菱形对称架构结合:
分配的过程可以呈现为序列图,作为动态的领域设计模型,它与静态的领域设计模型共同组成领域设计模型。然则,绘制序列图总是不太方便,于是,我提出了编写序列图脚本的方法。以提交订单业务服务为例,分解获得的子任务树为:
根据职责分配的规则,业务服务分配给远程服务与本地服务,组合任务分配给领域服务,原子任务分配给聚合或端口,就可以编写出如下的序列图脚本:
OrderController.placeOrder(placingOrderRequest) { // 业务服务对应远程服务 OrderAppService.placeOrder(placingOrderRequest) {//应用服务的方法体现服务价值 OrderService.placeOrder(order) { // 领域服务对应组合任务,避免领域逻辑泄露到应用服务 OrderService.validate(order) { // 领域服务对应组合任务 Order.validate(); // 聚合承担原子任务 InventoryCheckingClient.check(order); // 客户端端口指向库存上下文的边界服务 } OrderRepository.save(order); // 资源库端口操作订单数据表 ShoppingCartService.removeItems(customerId,cartItems) { // 领域服务对应组合任务 ShoppingCartRepository.cartOf(customerId); // 资源库端口操作购物车数据表 ShoppingCart.removeItems(cartItems); // 聚合承担原子任务 ShoppingCartRepository.save(shoppingCart); // 资源库端口操作购物车数据表 } } OrderPlacedPublisher.publish(orderPlacedEvent); // 发布者端口发布订单已提交的应用事件 } }
序列图脚本的创意并非我的创举,而是ZenUML给予我的启发。我还在ThoughtWorks的时候,我的Sponsor肖鹏正在打磨这款工具。我们二人都认同UML的序列图对于领域建模与设计颇有助力。一方面,序列图这一可视化方式可以提供给设计者一些特征,用于甄别设计的坏味道;另一方面,绘制序列图时,是由外向内逐层递进的,可以更好地站在调用者的角度去思考设计,消息的定义也会产生一种驱动力。我在为GitChat编写领域驱动设计课程时,就想到了这一工具,它提供的脚本语法非常接近Java语法,于是,我就采用拿来主义,将其搬到我的文章里,在融入业务服务、菱形对称架构、角色构造型后,组成了服务驱动设计,其完整过程如下图所示:
在《解构领域驱动设计》一书即将出版前,我准备修改针对菱形对称架构提供的一个案例,预备在代码库中增加提交订单的序列图脚本。忽然想起之前与肖鹏交流时,他曾提及ZenUML已经为IntelliJ IDEA开发了插件。果然在Settings -> Plugins中找到了ZenUML的插件:
它的使用方式非常简单,在安装了该插件后,你可以在代码库的任意位置(建议在项目根目录下定义一个文件夹),新建一个扩展名为.zen的文件,然后在文件内根据语法编写序列图脚本,工具就可以自动生成序列图了:
在上图右上方的View工具栏上,还可以切换视图类型,从左到右依次为:
- 仅显示编辑器:此时只会显示时序图脚本
- 显示编辑器和预览:如上图所示,同时显示时序图脚本和预览的时序图效果
- 仅显示预览:此时只会显示序列图
- 在浏览器中打开
如果将ZenUML工具运用到服务驱动设计方法中,即可在领域设计建模阶段尝试通过IDE建模,分析需求后,尝试编写序列图脚本,然后对照生成的序列图对脚本进行调整。调整的成本很低,完全可以随时修订。一旦确定了最终的序列图,即可按照测试驱动开发的流程,优先为聚合承担的原子任务和组合任务编写测试用例,通过测试驱动出该业务服务的实现代码。
ZenUML的功能当然不限于此,在驱动出最终的实现代码后,也可以将真实代码转换为序列图。例如在IntelliJ IDEA中,打开已经实现好的远程服务类OrderController,将光标移到要生成序列图的方法体内,右键弹出快捷菜单,即可看到如下的菜单项:
选择该菜单项,就会自动生成序列图脚本与对应的序列图,生成的文件为buffer{n}.zen:
如果你不喜欢它默认提供的呈现样式,也可以到Languages & Frameworks中找到ZenUML,添加CSS规则,以改变呈现样式:
ZenUML除了Web APP之外,还提供了Confluence插件,以便于我们编写设计文档。不出意料之外,它也为主流的浏览器提供了扩展,例如,在Microsoft Edge浏览器中可找到ZenUML Sequence扩展,安装后,工具栏会出现它的图标,打开,即可输入序列图脚本生成序列图:
真的是太方便了!ZenUML简直就是为服务驱动设计量身定做的。至于该怎么实践服务驱动设计,在《解构领域驱动设计》书中你可以找到答案。该如何使用ZenUML?那就太简单了,它的脚本语法基本和Java相同,使用也非常简单,无论是通过浏览器还是IDE,实际去使用一下,很快就能理解它的价值。
如果你的开发流程和开发工具中需要序列图,也可以直接在系统中把ZenUML作为一个前端库进行集成。目前已经有多家企业在自己的构建流程中集成了ZenUML。某国内企业在构建过程中使用ZenUML在文档中嵌入序列图;某国外企业则开发了Python转ZenUML工具,从代码直接生成序列图。ZenUML的文本转序列图的功能以免费的形式发布在npm上面。ZenUML开发者提供(有限的)免费技术支持。