概述###
业务编排型系统,主要是指那些通过组合基础服务来实现具体业务的系统。这样的系统,通常不需要考虑复杂问题的技术方案和复杂业务的建模。但这样的系统,如果没有一些准则性的指导和约束,很容易写成一团缠结的“意大利面”或荆棘丛生的代码丛林,难以维护和开拓。
那么,怎么保证业务编排系统的可持续维护呢 ? 首先,需要明白:关键点在哪里。
业务编排系统,要面临的挑战是:需求复杂多变。如何应对变化同时保持代码结构清晰易懂 ?这实际上是个信息组织设计的问题。需要使三个要素清晰化:
- 依赖服务
- 编排流程
- 业务逻辑
整个信息设计及代码组织,都是围绕这三个要素展开。
法则###
工程约定####
要管理这样的工程应用,首要的一件事,不是立即着手去做,而是建立一些工程约定,这些约定确保代码放在大致合适的位置。
- Api : 对外公开的服务和接口定义
- Biz / Service : Api 的具体实现,通常是组合 Domain 和 Dependency ,辅以必要的业务逻辑来实现指定的服务和功能。
- Dependence : 外部的服务依赖,都放在依赖层;需要提供适配转换。
- Common : 公共层,通常放置工具类、常量、异常、应用配置等
- Domain : 通常用于放置含有行为能力的实体对象及交互、流程编排。
- Config : 工程配置相关
- Deploy : 工程部署相关
依赖与适配####
有基础服务依赖,就需要隔离依赖。需要设置一个“依赖层”,专门存放各种依赖服务。这层负责调用调用各种底层服务,方便业务层进行复用和组装。
此外,还需要一个适配层,将客户端使用与底层的对象进行隔离。考虑一种场景,如果不做适配的话,底层返回对象就会遍布到工程里的各处。如果某天这个底层接口的返回对象改变了,或者要换另一个接口,返回对象的结构发生了很大改变。那么上游使用端可就是一场“小地震”了。从设计角度来看,这叫做“上下文边界适配”。
当然,需要根据场景来抉择。如果是复杂的对象,才需要适配和转换;简单的接口调用和返回对象,就不需要多此一举了。
信息组织####
复杂的返回对象,应当按照信息分类原则来组织信息。比如订单详情:
- 订单基本信息:订单标识、归属、标签、状态、时间、来源等
- 商品基本信息:商品标识、规格、价格、类目、数量、活动等
- 支付信息;买家与收货人;备注与加星等
- 发货与物流等
- 退款与维权等
- 营销或优惠等
- 扩展信息: 特定业务相关,比如拼团、分销、精选等
- 控制信息: 按钮、文案的显示与隐藏,权限、设置的开启与关闭等
在获取信息的时候,也应该有个层次。通常可分为两个层次:获取源数据、对源数据进行格式化处理。
- 获取源数据。可以建立一个接口,通过增加新增接口实现来新增源数据获取。这里,源数据获取通常是可以并发的。
- 对源数据进行格式化处理。可以建立一个接口,通过新增接口实现源数据格式化。这里,需要进行格式化处理的顺序控制。顺序控制,既可以通过注解的方式实现,也可以通过配置组件执行顺序来实现。
- 强关联分类信息的获取放在同一个包下;在同一个分类下,语义关联不紧密的信息应该使用短函数分离开,作为一个关注点而存在。
流程清晰化####
流程清晰性是理解业务系统的关键所在。 而清晰性的体现,则在于是否能够快速理解系统里的功能服务实现。这些功能服务实现,总是由查询与命令组合而成。查询是从外部服务获取一些数据;命令则是根据一些前置条件向外部服务发送指令,请求执行某种操作产生影响。要想获得清晰性,就必须使得查询与命令的意图完全地凸显出来,让细节隐藏。
然而,常常会遇到两种情形。
一种情形是,大段大段逻辑的长方法,虽然有时会使用注释来隔开,但不起卵用。常常得穿越大片“丛林”,找到目标靶点,这样往往很蛋疼。遇到这种情况,需要耐心地将各个独立的部分分离到一个个短函数里,每个短函数解决一个子流程或关注点。主流程只是将这些短函数串联起来,掌控全局。
另一个情形是,时而会看到一段实现细节的代码,半路杀出来,将原来清晰性的步骤冲刷。一次,两次,三次,逐渐,整个流程就会淹没在大量细节中时隐时现。尤其要注意的是参数构建,切忌随意扔在主流程里,要抽离短函数处理。很容易冲刷掉主流程的清晰性。
要解决这种问题,必须保持克制的约定。坚持不在主流程加任何实现细节的东西,只加入有语义的步骤说明。在 CodeReview 的时候要尤其注意规避这些破坏流程清晰性的代码进入主库。
语义细节分离####
常常会看到一些条件或循环的业务代码,散落在流程中。这些业务代码表达了某些甚至是很重要的业务关注点,却因为流落在流程中而备受冷落。导致的问题是:1. 流程变长,针对流程的单测编写和通过变得困难; 2. 针对这些重要业务点的单测缺失,以后改起来容易出BUG; 3. 想要复用这些业务点,不得不重复写,出现变化的时候就不得不修改多处。
要解决这种问题,需要“语义细节分离”。确定这些业务关注点的语义,将其用纯函数及命名来表达。这样,隔离了这些关注点,凸显了业务语义,方便了写单测,还可实现业务点组合复用的目的。
技术业务分离####
开发同学常常会将一些与业务无关的技术性逻辑挟裹在业务代码里,读起来比较费劲,不容易立即看出业务逻辑。此外,技术性的逻辑也无法复用。如何应对,详见: “将通用性的技术逻辑与差异性的业务逻辑相分离”
业务配置化####
要有更多时间创造和创新,就必须减少一些不必要的时间消耗。比如发布系统,其实就是时间耗损的源头之一。发布系统不能频繁,一则影响系统提供服务的稳定性,二则要沟通和耗费很多时间。
有时,会看到有些业务逻辑经常变动,每次改动都很小,每次都要发布系统,耗费时间。应当将这些微小的变化点隔离出来,使用一个轻量级的配置平台,通过一些简单的配置和动态更新推送机制,实现此类改动,减少系统发布频次。
策略模式####
常常会在代码里看到 if () { 流程一 } else { 流程二 } , 流程一和流程二实现的意图基本一样。可以考虑使用策略模式来分离。定义一个接口,将流程一和流程二做成接口的两个实现,然后根据不同场景来选取。
编程实现####
编程实现放在最后,但并非最不重要。编程写法的优雅,会带来轻松和乐趣,减轻在代码丛林中穿梭的劳累,同时也能让人快速地理解其意图。
比如使用 foreach-if-add 的写法,试着用stream去重写它;比如关联两个有相同键值的列表,试着去抽象这个流程,将其变成可通用的方法。
写得足够多,就会发现一些基础实现模式在反复出现。比如分页模式。试着去提炼它,变成可复用的模式、结构和方法,使得实现相似的功能只需要一两行代码,不仅加速了工作效率,也锻炼了抽象思维能力。
小结###
本文探讨了实现业务编排型系统的持续可维护性的一些法则:工程约定、依赖与适配、业务组织、流程清晰化、语义细节分离、技术业务分离、业务配置化、策略模式。适当滴组合法则,就能在应对多变的业务的同时,持续保持系统清晰的组织结构和可维护性。