如何形成统一设计风格-实践篇
作者 | 木沉来源 | 阿里技术公众号一 背景在上一篇《业务团队如何统一架构设计风格?》中,探讨了一种业务架构的设计规范,以期达到这些目标:用标准约束技术细节;用技术工具而非文档推行标准;持续重构而非造新轮子;重视业务建模。但通篇表述较为抽象。本篇将总结团队近来的架构演进工作,以更具体的技术细节,详细阐释该理念,作为“统一业务设计风格”的实践篇。文中详述了多个层面的设计规约和基于规约的搭建方式,并在末尾回答了上一篇的诸多疑问。二 总览上图以电商产品为例,展示了一套标准框架的各层设计单元。先简单了解下概念,下一章节会详细解释各层的设计规约和搭建方式:产品模式层以产品合约描述完整的功能列表;以签署人身份来定位产品功能的适用场景;以合约分组来描述一个独立完备的功能域,分组的集合就是产品功能的范围和边界。通过对合约分组进行组装,可以快速搭建商业产品。业务模型层为了减少不同技术同学对领域进行建模的风格差异,我们对业务模型的使用场景做了诸多约定,串联起仓储管理/业务流程/业务组件等基础模块。所有人更关注于业务在模型上的表达,而大大减少了对实现细节的关注。基于对领域的分析,可以快速搭建业务模型。业务流程层用一套标准的业务流程框架,描述业务模型的完整执行流程:业务组件是一套高内聚的业务功能集合,基于组件配置将业务模型的信息适配为标准参数,交由基础设施执行具体功能;流程引擎负责创建和管理流程实例,接收指令来触发组件动作的执行,并实现状态推进/条件跳转和异常处理等分支管控的需求。通过对业务组件/基础设施的抽象和沉淀,可以快速搭建业务流程。数据视图层用一套标准的数据流机制,来满足视图层的定制化需求:数据流订阅器用于采集数据,物理来源包含区块链跨链数据/业务DB数据/文件系统数据/离线任务数据等;数据流消费器用来加工原始数据,生成展示层数据/待核对数据/数据指标等等。订阅器确保了数据来源的稳定和低成本的快速接入,消费器则交由技术同学自行定制业务逻辑。在不干扰领域建模的基础上,可以快速搭建数据视图。三 规约详解1 产品模式产品合约1)规约产品合约以全局视角,描述完整的业务模式,包括:服务的目标客户,依赖的业务领域,输出的服务等等产品合约的内容是一份静态描述文件,需要由签署身份列表来界定使用场景2)实例以电商产品为例,商家单独签署的产品合约被作为商家合约,描述了商品的上架要求;商家+平台+买家共同签署的产品合约,则适用于交易下单场景。3)搭建新增/修改低代码:基于业务需求,在产品中心设计产品模板,明确合约分组和具体内容使用:接入时编码,一次性:在业务系统内编写对应产品合约和签署身份的模型类,完成和产品中心的对接,包括合约的创建/失效,基于签署身份的合约查询等等合约分组1)规约合约分组以局部视角,描述某个高度内聚的业务领域所提供的功能和依赖的配置信息,包括:业务模型,业务服务,业务流程,业务组件等等多个合约分组共同组成一个可交付业务的产品合约2)实例电商产品合约下,商品分组描述了商品上架的流程和配置,下单分组约束了订单创建的流程和服务信息,退货分组则说明了退货流程和买家能够享受的客户服务。3)搭建新增/修改低代码:以元数据的方式定义一个合约分组,包含模型/流程/配置等等,每一个配置都可以用键路径/配置值类型和限制等描述使用硬编码:在业务系统内定义合约分组的模型类,完成与产品合约内容交互的写入和读取,在业务代码处显式获取业务分组实例低代码:搭建合约查询->分组解析->配置获取的通用框架(引入缓存避免重复查询),业务层只需要通过元数据描述,就可以获取对应分组内的配置信息2 业务领域模型1)规约业务模型描述一个领域内的核心业务实体,是唯一贯通业务流程和业务组件的业务实例一个业务模型内可以关联其他模型,但应避免出现循环依赖一个完备的业务模型描述需要包含:数据模型,视图模型,业务模型/数据模型/视图模型的三者转换,业务模型仓储等2)实例退货业务,基于退货单推进业务流程,各业务组件从退货单获取必要的业务信息,执行退货/退款/通知等业务功能;退货单关联自一个正向订单,但正向订单不可反向依赖退货单;一个退货单模型对应一张主单据表和多张退货明细表,仓储需要负责完成业务模型<->数据模型的双向读写3)搭建硬编码:编写业务模型(Model)/数据模型(DO)/数据交互(Mapper)/视图模型(VO)/转换层(Converter)/仓储(Repository)等等低代码:用元数据描述,自动生成DO/VO/Mapper/Converter;基于底座提供的仓储组件,也可以通过元数据描述,自动生成业务模型仓储的实例服务1)规约1、业务服务是一套以业务领域为单位(interface)作聚合,开放给内外所有使用方的最小业务功能单元(method)2、业务服务需要一套定义规范(annotation/aop等),对每一个功能单元有清晰直观的元数据描述,用以实现服务发现/文档生成/权限管控/稳定性保障等等。元数据包括:业务域,业务动作,读/写,错误码范围,返回值模型等等3、业务服务的入参,限制为一个sysParam和一个bizParam,前者为调用来源/幂等ID/产品码/租户ID等系统参数,后者为各业务自行定义的模型参数,建议为可全链路透传(rpc->api->flow->component)的POJO4、业务服务以Result形式返回,错误码尽量控制在元数据描述的范围内,不泄漏任何exception给调用方。返回的业务信息,建议为POJO或VO5、业务服务不局限于调用方的物理来源,只需要在对接层增加简单的转换逻辑,做授权管控即可6、写服务的实现,需要有事务管理机制2)实例public interface DemoOrderService {
/**
* 下单申请
* @param sysParam sysParam
* @param bizParam bizParam
* @return result
*/
@ApiFunction(apiType = ApiType.SUBMIT, funcBiz = "ORDER",funcAction = "APPLY",
returnType = OrderApplyResponse.class, errorCodeType = CommonErrorCodeEnum.class)
CommonResult<OrderApplyResponse> apply(ApiReqSysParam sysParam, OrderApplyInfo bizParam);
}3)搭建新增/修改定义-低代码:基于元数据描述,自动生成interface+method+errorcode+POJO等等实现硬编码:简单需求/不可模板化/无法流程化的业务需求,直接编码低代码:对于标准的流程发起服务(申请上架/申请下单/申请退货),用模板实现合约分组加载->流程配置加载->流程初始化(幂等)->流程触发->结果处理;对于标准的流程推进服务(通知回执/调度推进),用模板实现流程配置加载->流程触发->结果处理等等。随着更多服务场景的出现,可以有更多模板化的业务服务。使用硬编码:与所有interface的使用一样,组装请求->调用->处理结果低代码:基于元数据描述和业务配置,将当前业务对象/外部参数映射为服务入参的POJO,异常处理模板化,成功返回的结果以同样方式映射回业务对象或外部响应流程1)规约1、Flow用于描述一个完整的业务流程,基于单个业务模型,推进一个或多个业务子环节2、对于单个业务模型的同一类型业务流程,可以有多个Flow定义,以满足不同业务模式的定制需求3、Flow包含迁转 (transition) ,组件 (component) 和动作 (action) 三级结构,运作原理如下:每次触发 (operate) 对应于组件的一次action,所有action都成功的component会完结,而所有component都成功的transition将会触发Flow和业务模型的状态迁转。4、Flow的目标是将复杂流程拆解成多个原子化的业务动作,相互解耦5、Flow需要结合业务服务/消息/调度等调用入口的触发,才能实现完备的流程推进6、Flow需要依赖外部调用方提供事务管理机制(通常是业务服务),需要依赖业务模型仓储来控制模型的加载和存储2)实例3)搭建新增/修改低代码:Flow自身的运作由底座组件支撑,只需一次性编码;若需要定义业务流程,可基于业务组件模板和业务模型,动态生成Flow配置文件;加上版本控制和隔离机制,就可以防止兼容性问题使用硬编码:Flow初始化场景,从当前业务领域的合约分组中,获取需要的Flow配置,初始化流程并推进;Flow推进场景,基于modelId+modelType+operate+request,可以用模版化代码自动触发低代码:通过对合约分组中Flow配置的标准化,可以将Flow初始化场景也以模板化的方式实现;当一个现有业务服务需要支持新定制的业务流程时,只需调整合约内的配置即可组件1)规约1、业务组件是某一类业务动作的聚合,面向业务功能设计,不局限于任何一个业务模型2、业务组件的业务动作,是原子化的最小业务单元,粒度暂无强制要求,但以解耦和复用程度为衡量依据;建议其依赖一个到多个基础设施/业务服务,以模板化的方式提供标准的业务动作实现3、对于某个业务模型,业务组件通过开放适配器(详见【基础设施-适配】)的方式支持受控定制,或以完全复写的方式实现排他定制(不允许其他业务复用)4、所有的核心业务逻辑,都应收归到业务组件层及其以下(无流程的简单业务服务除外),包括但不限于:参数校验,业务校验,重入/幂等控制,业务模型变更,合约分组变更,计算规则,外部服务交互等等5、业务组件需要一套定义规范(xml/annotation等),对其支持的业务动作和业务模型有清晰直观的元数据描述,用以搭建业务流程。元数据包括:业务动作列表和对应的触发点(operate),支持的业务模型列表2)实例核身组件定义类public interface BizModelDiscountComponent<T extends BizModel> extends BizModelComponent<T> {
/**
* 占用优惠
* @param context
*/
void occupy(FlowContext context);
/**
* 退回优惠
* @param context
*/
void refund(FlowContext context);
}核身组件元数据配置核身组件模板化实现适配器Adapter的解释,详见【模型适配】小节public abstract class AbstractBizModelDiscountComponent< T extends BizModel> implements BizModelDiscountComponent< T> {
@Resource
private DiscountApiService discountApiService;
@Override
public void occupy(FlowContext context) {
// TODO AdapterConfigInfo根据context从当前合约中获取
T bizModel = (T) context.getBizModel();
getDiscountAdapter().processOnOccupyResult(
bizModel,
discountApiService.occupy(getDiscountAdapter().toOccupyInfo(bizModel, new AdapterConfigInfo()))
);
}
@Override
public void refund(FlowContext context) {
// TODO AdapterConfigInfo根据context从当前合约中获取
T bizModel = (T) context.getBizModel();
getDiscountAdapter().processOnRefundResult(
bizModel,
discountApiService.refund(getDiscountAdapter().toRefundInfo(bizModel, new AdapterConfigInfo()))
);
}
@SuppressWarnings("unchecked")
protected BizModelToDiscountAdapter< T> getDiscountAdapter(){
return (BizModelToDiscountAdapter< T>) FlowInstanceFactory.instanceBizAdapter(
"DISCOUNT", (Class< ? extends BizModel>) TypeUtils.getRealClassOfParameterizedType(this));
}
}3)搭建新增/修改硬编码:全新业务组件基本无法低代码化,需要开发有足够的设计思维和大局观,权衡复用度和成本后实现初版;随着业务发展,逐步抽象出模板化的业务组件实现;很多场景下,如果避免不了复杂的定制逻辑,可以自行以策略/职责链/工厂等多种设计模式落地,这依赖于开发者的建模能力,不做强制要求低代码:已有的业务组件应用于新业务模型的场景,如果已经抽象出合约配置+适配器+基础设施的标准模板,只需做合约配置即可(通知/核身/存证上链等场景适合)使用低代码:在Flow底座中完成业务组件的编排/发现和触发,一次性编码;完成Flow配置,即完成业务组件的装配3 基础设施注:此处的基础设施与DDD中的概念有很大差异,请勿混淆规约基础设施是一套以高复用高内聚低变化的外部服务能力为单位(interface)作聚合,开放给业务服务/业务组件使用的最小功能单元(method)基础设施可以是对渠道能力的封装,如外部商家渠道服务/跨境渠道服务等;也可以是对通用技术能力的封装,如优惠服务/商品服务/客户服务等基础设施和业务服务的差异在于:前者的核心功能通常由外部服务提供,在当前系统内的核心职责是参数组装/场景识别/返回解析和异常处理基础设施的定义不依赖于外部服务,入参为自行定义的标准POJO,返回值同样以Result封装,屏蔽外部服务的exception和业务异常,业务返回同样是标准POJO实例基础设施-信息通知public interface NotifyGateway {
/**
* 通知(邮件/短信/站内信)
* @param notifyInfo
* @return
*/
CommonResult<NotifyResponse> notify(NotifyInfo notifyInfo);
}搭建新增/修改硬编码:基础设施的接入通常是一次性的,低代码的价值不易发挥使用硬编码:在业务服务/业务组件等调用方代码中,组装入参->调用->解析返回低代码:在业务组件中,基于下文将介绍的适配机制,可以实现:合约配置+模板化业务组件,低代码复用现有基础设施4 模型适配规约模型适配用于衔接业务模型和基础设施/业务服务,实现模型->入参和返回->模型的双向处理在模板化的业务组件中,适配器和基础设施/业务服务的调用链已经固化,各业务模型的组件实例只需要实现对应的适配器,即可完成业务定制适配器通常与产品合约配置结合,描述业务模型->基础设施/业务服务入参的映射关系实例适配器-业务模型->网银签名public abstract class BizModelToDiscountAdapter< U extends BizModel> implements BizModelAdapter< U> {
@Override
final public String getType(){
return "DISCOUNT";
}
/**
* 生成扣减申请
* @param bizModel
* @return
*/
abstract public OccupyInfo toOccupyInfo(U bizModel, AdapterConfigInfo configInfo);
/**
* 处理扣减结果
* @param bizModel
* @param result
*/
abstract public void processOnOccupyResult(U bizModel, CommonResult< OccupyResponse> result);
//...
}订单模型Order,需要使用优惠扣减服务时,需要实现适配器BizModelToDiscountAdapter:@BizAdapter
public class OrderToDiscountAdapter extends BizModelToDiscountAdapter< Order> {
@Override
public List< ConfigDef> getConfigDefs() {
return Lists.newArrayList(
ConfigEnum.DISCOUNT_TYPE,
ConfigEnum.DISCOUNT_TERM
);
}
@Override
public OccupyInfo toOccupyInfo(Order bizModel, AdapterConfigInfo configInfo) {
// 解析出客户选择的优惠类型
return new OccupyInfo();
}
@Override
public void processOnOccupyResult(Order bizModel, CommonResult< OccupyResponse> result) {
// TODO 根据扣减成功的优惠,重新计算订单金额
}
// ...
}搭建新增/修改定义-硬编码:当业务组件和基础设施/业务服务出现调用关系时首次定义,通常不再变更实现-低代码:可以用一套灵活的合约配置描述映射关系,实现一次编码后只需配置维护;但是,这既依赖于DSL级别的描述能力,也需要业务模型和基础设施/业务服务的设计者,都具备较高的抽象能力,成本较高使用硬编码:当业务开发抽象出可模板化的业务组件时,即完成了首次接入;当基础设施/业务服务出现新模式时,需要进行适配调整四 总结啰嗦了这么多,为避免被过度细节冲淡主题。最后以几个问题做个小结:1 业务设计规范体现在哪里?架构层面,从产品合约->业务领域->基础设施,我们对应用做了模块拆解,在不同层面设计了业务规约,约束了各模块的职责;技术层面,通过多个底座组件,一定程度上实现了平台和业务定制的隔离,限制了业务细节的无序散布。2 业务设计只有合适没有标准,为何要强制规范?规范的目的不是标准本身,本文提出的标准也未必适合所有问题域。想传达的是,团队内需要有业务设计的某种共识和沉淀,在每次迭代需求和每次项目产出的基础上,持续积累持续重构持续优化,这对新人融入/个人成长和团队协作都很有帮助。3 如何快速支撑业务,研发效能提升体现在哪里?需要明确的是,对于全新的业务需求,不会带来明显的效能提升,甚至会为了满足设计规范,带来一定程度的额外成本。但当多人协作,工作交接,或是现有功能部分可复用的场景下,会减少很多不必要的沟通和维护成本。举例来说,当一个业务需求出现时,研发人员需要做如下判断:业务模型:是否需要新的业务模型,是否需要调整现有模型业务服务:xxxxxxxxxxxx业务服务,xxxxxxxxxxxx现有服务业务流程:xxxxxxxxxxxx业务流程,xxxxxxxxxxxx现有流程业务组件:xxxxxxxxxxxx业务组件,xxxxxxxxxxxx现有组件基础设施:xxxxxxxxxxxx基础设施,xxxxxxxxxxxx现有设施产品合约/合约分组:基于上述判断,评估产品合约和合约分组的组装带来的效能提升有这样几点:业务领域的每个模块互相解耦,研发过程并行化,投入人员1+1可以=2;改造范围更易于定位,资源评估更为准确,进度把控更加清晰;针对频繁变动且成本过高的模块,进行针对性的重构,影响范围可控;上文中的很多处规约,都有潜在的低代码化可能,能进一步提升搭建效率。大数据可视化DataV课程点击这里,了解详情。
如何形成统一设计风格-实践篇
背景在上一篇《业务团队如何形成统一的设计风格》中,探讨了一种业务架构的设计规范,以期达到这些目标:用标准约束技术细节;用技术工具而非文档推行标准;持续重构而非造新轮子;重视业务建模。但通篇表述较为抽象。本篇将总结团队近来的架构演进工作,以更具体的技术细节,详细阐释该理念,作为“统一业务设计风格”的实践篇。文中详述了多个层面的设计规约和基于规约的搭建方式,并在末尾回答了上一篇的诸多疑问。总览上图以电商产品为例,展示了一套标准框架的各层设计单元。先简单了解下概念,下一章节会详细解释各层的设计规约和搭建方式:产品模式层 以产品合约描述完整的功能列表;以签署人身份来定位产品功能的适用场景;以合约分组来描述一个独立完备的功能域,分组的集合就是产品功能的范围和边界。通过对合约分组进行组装,可以快速搭建商业产品。业务模型层 为了减少不同技术同学对领域进行建模的风格差异,我们对业务模型的使用场景做了诸多约定,串联起仓储管理/业务流程/业务组件等基础模块。所有人更关注于业务在模型上的表达,而大大减少了对实现细节的关注。基于对领域的分析,可以快速搭建业务模型。业务流程层 用一套标准的业务流程框架,描述业务模型的完整执行流程:业务组件是一套高内聚的业务功能集合,基于组件配置将业务模型的信息适配为标准参数,交由基础设施执行具体功能;流程引擎负责创建和管理流程实例,接收指令来触发组件动作的执行,并实现状态推进/条件跳转和异常处理等分支管控的需求。通过对业务组件/基础设施的抽象和沉淀,可以快速搭建业务流程。数据视图层 用一套标准的数据流机制,来满足视图层的定制化需求:数据流订阅器用于采集数据,物理来源包含区块链跨链数据/业务DB数据/文件系统数据/离线任务数据等;数据流消费器用来加工原始数据,生成展示层数据/待核对数据/数据指标等等。订阅器确保了数据来源的稳定和低成本的快速接入,消费器则交由技术同学自行定制业务逻辑。在不干扰领域建模的基础上,可以快速搭建数据视图。规约详解产品模式产品合约规约产品合约以全局视角,描述完整的业务模式,包括:服务的目标客户,依赖的业务领域,输出的服务等等产品合约的内容是一份静态描述文件,需要由签署身份列表来界定使用场景实例以电商产品为例,商家单独签署的产品合约被作为商家合约,描述了商品的上架要求;商家+平台+买家共同签署的产品合约,则适用于交易下单场景。搭建新增/修改低代码:基于业务需求,在产品中心设计产品模板,明确合约分组和具体内容使用:接入时编码,一次性:在业务系统内编写对应产品合约和签署身份的模型类,完成和产品中心的对接,包括合约的创建/失效,基于签署身份的合约查询等等合约分组规约合约分组以局部视角,描述某个高度内聚的业务领域所提供的功能和依赖的配置信息,包括:业务模型,业务服务,业务流程,业务组件等等多个合约分组共同组成一个可交付业务的产品合约实例电商产品合约下,商品分组描述了商品上架的流程和配置,下单分组约束了订单创建的流程和服务信息,退货分组则说明了退货流程和买家能够享受的客户服务。搭建新增/修改低代码:以元数据的方式定义一个合约分组,包含模型/流程/配置等等,每一个配置都可以用键路径/配置值类型和限制等描述使用硬编码:在业务系统内定义合约分组的模型类,完成与产品合约内容交互的写入和读取,在业务代码处显式获取业务分组实例低代码:搭建合约查询->分组解析->配置获取的通用框架(引入缓存避免重复查询),业务层只需要通过元数据描述,就可以获取对应分组内的配置信息业务领域模型规约业务模型描述一个领域内的核心业务实体,是唯一贯通业务流程和业务组件的业务实例一个业务模型内可以关联其他模型,但应避免出现循环依赖一个完备的业务模型描述需要包含:数据模型,视图模型,业务模型/数据模型/视图模型的三者转换,业务模型仓储等实例退货业务,基于退货单推进业务流程,各业务组件从退货单获取必要的业务信息,执行退货/退款/通知等业务功能;退货单关联自一个正向订单,但正向订单不可反向依赖退货单;一个退货单模型对应一张主单据表和多张退货明细表,仓储需要负责完成业务模型<->数据模型的双向读写搭建硬编码:编写业务模型(Model)/数据模型(DO)/数据交互(Mapper)/视图模型(VO)/转换层(Converter)/仓储(Repository)等等低代码:用元数据描述,自动生成DO/VO/Mapper/Converter;基于底座提供的仓储组件,也可以通过元数据描述,自动生成业务模型仓储的实例服务规约业务服务是一套以业务领域为单位(interface)作聚合,开放给内外所有使用方的最小业务功能单元(method)业务服务需要一套定义规范(annotation/aop等),对每一个功能单元有清晰直观的元数据描述,用以实现服务发现/文档生成/权限管控/稳定性保障等等。元数据包括:业务域,业务动作,读/写,错误码范围,返回值模型等等业务服务的入参,限制为一个sysParam和一个bizParam,前者为调用来源/幂等ID/产品码/租户ID等系统参数,后者为各业务自行定义的模型参数,建议为可全链路透传(rpc->api->flow->component)的POJO业务服务以Result形式返回,错误码尽量控制在元数据描述的范围内,不泄漏任何exception给调用方。返回的业务信息,建议为POJO或VO业务服务不局限于调用方的物理来源,只需要在对接层增加简单的转换逻辑,做授权管控即可写服务的实现,需要有事务管理机制实例public interface DemoOrderService { /** * 下单申请 * @param sysParam sysParam * @param bizParam bizParam * @return result */ @ApiFunction(apiType = ApiType.SUBMIT, funcBiz = "ORDER",funcAction = "APPLY", returnType = OrderApplyResponse.class, errorCodeType = CommonErrorCodeEnum.class) CommonResult<OrderApplyResponse> apply(ApiReqSysParam sysParam, OrderApplyInfo bizParam);}搭建新增/修改定义-低代码:基于元数据描述,自动生成interface+method+errorcode+POJO等等实现硬编码:简单需求/不可模板化/无法流程化的业务需求,直接编码低代码:对于标准的流程发起服务(申请上架/申请下单/申请退货),用模板实现合约分组加载->流程配置加载->流程初始化(幂等)->流程触发->结果处理;对于标准的流程推进服务(通知回执/调度推进),用模板实现流程配置加载->流程触发->结果处理等等。随着更多服务场景的出现,可以有更多模板化的业务服务。使用硬编码:与所有interface的使用一样,组装请求->调用->处理结果低代码:基于元数据描述和业务配置,将当前业务对象/外部参数映射为服务入参的POJO,异常处理模板化,成功返回的结果以同样方式映射回业务对象或外部响应流程规约Flow用于描述一个完整的业务流程,基于单个业务模型,推进一个或多个业务子环节对于单个业务模型的同一类型业务流程,可以有多个Flow定义,以满足不同业务模式的定制需求Flow包含迁转 (transition) ,组件 (component) 和动作 (action) 三级结构,运作原理如下:每次触发 (operate) 对应于组件的一次action,所有action都成功的component会完结,而所有component都成功的transition将会触发Flow和业务模型的状态迁转。Flow的目标是将复杂流程拆解成多个原子化的业务动作,相互解耦Flow需要结合业务服务/消息/调度等调用入口的触发,才能实现完备的流程推进Flow需要依赖外部调用方提供事务管理机制(通常是业务服务),需要依赖业务模型仓储来控制模型的加载和存储实例<?xml version="1.0" encoding="UTF-8"?><flow id="OrderApply" version="001" desc="标准下单流程"> <config> <model class="xxx.xxx.Order"/> </config> <init type="INIT" desc="初始化"> <action operate="INIT.INIT"/> </init> <transitions> <transition from="INIT" to="ITEM_OCCUPIED"> <component type="ITEM" desc="扣减库存"> <action operate="ITEM.OCCUPY"/> </component> </transition> <transition from="ITEM_OCCUPIED" to="DISCOUNT_OCCUPIED"> <component type="DISCOUNT" desc="扣减优惠"> <action operate="DISCOUNT.OCCUPY"/> </component> </transition> <transition from="DISCOUNT_OCCUPIED" to="SUCCESS"> <component type="NOTIFY" desc="下单成功通知"> <action operate="NOTIFY.SELLER"/> <action operate="NOTIFY.BUYER"/> </component> </transition> </transitions></flow>搭建新增/修改低代码:Flow自身的运作由底座组件支撑,只需一次性编码;若需要定义业务流程,可基于业务组件模板和业务模型,动态生成Flow配置文件;加上版本控制和隔离机制,就可以防止兼容性问题使用硬编码:Flow初始化场景,从当前业务领域的合约分组中,获取需要的Flow配置,初始化流程并推进;Flow推进场景,基于modelId+modelType+operate+request,可以用模版化代码自动触发低代码:通过对合约分组中Flow配置的标准化,可以将Flow初始化场景也以模板化的方式实现;当一个现有业务服务需要支持新定制的业务流程时,只需调整合约内的配置即可组件规约业务组件是某一类业务动作的聚合,面向业务功能设计,不局限于任何一个业务模型业务组件的业务动作,是原子化的最小业务单元,粒度暂无强制要求,但以解耦和复用程度为衡量依据;建议其依赖一个到多个基础设施/业务服务,以模板化的方式提供标准的业务动作实现对于某个业务模型,业务组件通过开放适配器(详见【基础设施-适配】)的方式支持受控定制,或以完全复写的方式实现排他定制(不允许其他业务复用)所有的核心业务逻辑,都应收归到业务组件层及其以下(无流程的简单业务服务除外),包括但不限于:参数校验,业务校验,重入/幂等控制,业务模型变更,合约分组变更,计算规则,外部服务交互等等业务组件需要一套定义规范(xml/annotation等),对其支持的业务动作和业务模型有清晰直观的元数据描述,用以搭建业务流程。元数据包括:业务动作列表和对应的触发点(operate),支持的业务模型列表实例核身组件定义类public interface BizModelDiscountComponent<T extends BizModel> extends BizModelComponent<T> { /** * 占用优惠 * @param context */ void occupy(FlowContext context); /** * 退回优惠 * @param context */ void refund(FlowContext context);}核身组件元数据配置<componentTemplate type="DISCOUNT" desc="优惠"> <interface name="xxx.xxx.BizModelDiscountComponent"/> <bizModelMappings> <bizModelMapping> <bizModel class="xxx.xxx.Order"/> <componentEntry name="orderDiscountComponent"/> </bizModelMapping> <bizModelMapping> <bizModel class="xxx.xxx.RefundOrder"/> <componentEntry name="refundOrderDiscountComponent"/> </bizModelMapping> </bizModelMappings> <triggerMappings> <triggerMapping> <triggerTemplate operatePostfix="OCCUPY"/> <methodEntry name="occupy"/> </triggerMapping> <triggerMapping> <triggerTemplate operatePostfix="REFUND"/> <methodEntry name="refund"/> </triggerMapping> </triggerMappings></componentTemplate>核身组件模板化实现适配器Adapter的解释,详见【模型适配】小节public abstract class AbstractBizModelDiscountComponent<T extends BizModel> implements BizModelDiscountComponent<T> { @Resource private DiscountApiService discountApiService; @Override public void occupy(FlowContext context) { // TODO AdapterConfigInfo根据context从当前合约中获取 T bizModel = (T) context.getBizModel(); getDiscountAdapter().processOnOccupyResult( bizModel, discountApiService.occupy(getDiscountAdapter().toOccupyInfo(bizModel, new AdapterConfigInfo())) ); } @Override public void refund(FlowContext context) { // TODO AdapterConfigInfo根据context从当前合约中获取 T bizModel = (T) context.getBizModel(); getDiscountAdapter().processOnRefundResult( bizModel, discountApiService.refund(getDiscountAdapter().toRefundInfo(bizModel, new AdapterConfigInfo())) ); } @SuppressWarnings("unchecked") protected BizModelToDiscountAdapter<T> getDiscountAdapter(){ return (BizModelToDiscountAdapter<T>) FlowInstanceFactory.instanceBizAdapter( "DISCOUNT", (Class<? extends BizModel>) TypeUtils.getRealClassOfParameterizedType(this)); }}搭建新增/修改硬编码:全新业务组件基本无法低代码化,需要开发有足够的设计思维和大局观,权衡复用度和成本后实现初版;随着业务发展,逐步抽象出模板化的业务组件实现;很多场景下,如果避免不了复杂的定制逻辑,可以自行以策略/职责链/工厂等多种设计模式落地,这依赖于开发者的建模能力,不做强制要求低代码:已有的业务组件应用于新业务模型的场景,如果已经抽象出合约配置+适配器+基础设施的标准模板,只需做合约配置即可(通知/核身/存证上链等场景适合)使用低代码:在Flow底座中完成业务组件的编排/发现和触发,一次性编码;完成Flow配置,即完成业务组件的装配基础设施注:此处的基础设施与DDD中的概念有很大差异,请勿混淆规约基础设施是一套以高复用高内聚低变化的外部服务能力为单位(interface)作聚合,开放给业务服务/业务组件使用的最小功能单元(method)基础设施可以是对渠道能力的封装,如外部商家渠道服务/跨境渠道服务等;也可以是对通用技术能力的封装,如优惠服务/商品服务/客户服务等基础设施和业务服务的差异在于:前者的核心功能通常由外部服务提供,在当前系统内的核心职责是参数组装/场景识别/返回解析和异常处理基础设施的定义不依赖于外部服务,入参为自行定义的标准POJO,返回值同样以Result封装,屏蔽外部服务的exception和业务异常,业务返回同样是标准POJO实例基础设施-信息通知public interface NotifyGateway { /** * 通知(邮件/短信/站内信) * @param notifyInfo * @return */ CommonResult<NotifyResponse> notify(NotifyInfo notifyInfo);}搭建新增/修改硬编码:基础设施的接入通常是一次性的,低代码的价值不易发挥使用硬编码:在业务服务/业务组件等调用方代码中,组装入参->调用->解析返回低代码:在业务组件中,基于下文将介绍的适配机制,可以实现:合约配置+模板化业务组件,低代码复用现有基础设施模型适配规约模型适配用于衔接业务模型和基础设施/业务服务,实现模型->入参和返回->模型的双向处理在模板化的业务组件中,适配器和基础设施/业务服务的调用链已经固化,各业务模型的组件实例只需要实现对应的适配器,即可完成业务定制适配器通常与产品合约配置结合,描述业务模型->基础设施/业务服务入参的映射关系实例适配器-业务模型->网银签名public abstract class BizModelToDiscountAdapter<U extends BizModel> implements BizModelAdapter<U> { @Override final public String getType(){ return "DISCOUNT"; } /** * 生成扣减申请 * @param bizModel * @return */ abstract public OccupyInfo toOccupyInfo(U bizModel, AdapterConfigInfo configInfo); /** * 处理扣减结果 * @param bizModel * @param result */ abstract public void processOnOccupyResult(U bizModel, CommonResult<OccupyResponse> result); //...}订单模型Order,需要使用优惠扣减服务时,需要实现适配器BizModelToDiscountAdapter:@BizAdapterpublic class OrderToDiscountAdapter extends BizModelToDiscountAdapter<Order> { @Override public List<ConfigDef> getConfigDefs() { return Lists.newArrayList( ConfigEnum.DISCOUNT_TYPE, ConfigEnum.DISCOUNT_TERM ); } @Override public OccupyInfo toOccupyInfo(Order bizModel, AdapterConfigInfo configInfo) { // 解析出客户选择的优惠类型 return new OccupyInfo(); } @Override public void processOnOccupyResult(Order bizModel, CommonResult<OccupyResponse> result) { // TODO 根据扣减成功的优惠,重新计算订单金额 } // ...}搭建新增/修改定义-硬编码:当业务组件和基础设施/业务服务出现调用关系时首次定义,通常不再变更实现-低代码:可以用一套灵活的合约配置描述映射关系,实现一次编码后只需配置维护;但是,这既依赖于DSL级别的描述能力,也需要业务模型和基础设施/业务服务的设计者,都具备较高的抽象能力,成本较高使用硬编码:当业务开发抽象出可模板化的业务组件时,即完成了首次接入;当基础设施/业务服务出现新模式时,需要进行适配调整总结啰嗦了这么多,为避免被过度细节冲淡主题。最后以几个问题做个小结:业务设计规范体现在哪里?架构层面,从产品合约->业务领域->基础设施,我们对应用做了模块拆解,在不同层面设计了业务规约,约束了各模块的职责;技术层面,通过多个底座组件,一定程度上实现了平台和业务定制的隔离,限制了业务细节的无序散布。业务设计只有合适没有标准,为何要强制规范?规范的目的不是标准本身,本文提出的标准也未必适合所有问题域。想传达的是,团队内需要有业务设计的某种共识和沉淀,在每次迭代需求和每次项目产出的基础上,持续积累持续重构持续优化,这对新人融入/个人成长和团队协作都很有帮助。如何快速支撑业务,研发效能提升体现在哪里?需要明确的是,对于全新的业务需求,不会带来明显的效能提升,甚至会为了满足设计规范,带来一定程度的额外成本。但当多人协作,工作交接,或是现有功能部分可复用的场景下,会减少很多不必要的沟通和维护成本。举例来说,当一个业务需求出现时,研发人员需要做如下判断:业务模型:是否需要新的业务模型,是否需要调整现有模型业务服务:xxxxxxxxxxxx业务服务,xxxxxxxxxxxx现有服务业务流程:xxxxxxxxxxxx业务流程,xxxxxxxxxxxx现有流程业务组件:************业务组件,xxxxxxxxxxxx现有组件基础设施:xxxxxxxxxxxx基础设施,xxxxxxxxxxxx现有设施产品合约/合约分组:基于上述判断,评估产品合约和合约分组的组装带来的效能提升有这样几点:业务领域的每个模块互相解耦,研发过程并行化,投入人员1+1可以=2;改造范围更易于定位,资源评估更为准确,进度把控更加清晰;针对频繁变动且成本过高的模块,进行针对性的重构,影响范围可控;上文中的很多处规约,都有潜在的低代码化可能,能进一步提升搭建效率。
业务团队如何形成统一的设计风格
首次上线应用,面对业务框架搭建你是否曾感到无从下手?维护线上应用,面对大量历史包袱你是否正避坑不及深陷泥潭?为何同样是业务应用,不同人的设计风格千差万别?为何最初的设计经过多个迭代后总是面目全非?新人来到团队,怎样才能快速了解业务,不被大量技术细节折磨?如果你也有这些困扰,希望本文能提供些许帮助。初衷细节割裂架构业界的成熟应用框架有很多。无论是SpringMVC/SpringBoot还是SofaBoot,都对工程结构给出了明确的规范约定,职责边界看似非常清晰。但在实践中,再简单的业务应用都避免不了业务逻辑分散各处,打破module边界出现隐式耦合的现象。分散的业务细节必然导致应用架构的割裂,如果没有持续的重构调整,最终总会变得复杂臃肿(当然,是持续有新需求的前提下),老人沉默新人流泪,只能依靠天降猛男重做2.0。究其原因,个人认为主要在于:【框架灵活性过高】:应用框架给出的是工程规范,而非业务设计规范,为开发者保留了非常大的灵活性,一个业务功能可以有很多种实现方式。【架构约束力不足】:业务架构的搭建和维护是在不同时段由不同人分别投入的结果,设计思维的不同,自我要求的不同,项目进度压力的不同,都会对应用的现状产生影响。若以法律和道德的关系做类比,通用框架约束了技术编码的“法律”底线,而设计原则就是开发人员对自身的“道德”要求。在简单的业务场景下,满足需求是第一优先级,设计能力的诉求并不突出。但在多方协作的业务团队下(真实情况大多如此),没有统一的“道德标准”,将很难形成合力完成复杂项目。阿里巴巴开发规约在推进编码标准的道路上迈出了很大一步,极大提升了工程人员的专业素质,大大提高了“道德共识”。那么在业务架构设计的领域里,是否至少能在某个问题域内,也建立一套面向业务研发同学的“设计规约”。技术沉淀流失另一方面,进入阿里后,自身研发经历虽然并不多,但接触过不少优秀设计。这些产出无论是否最优方案,都体现出了技术同学对优秀设计的美好愿望和强大落地能力,也确实在一定的历史时期内高效地保障了业务发展。然而,令我困惑的是,尽管每个业务项目和业务产品都能沉淀出一些可复用的组件或框架,参与研发的同学也能总结出一套面向未来需求的设计原则和实践经验,但这些财富始终难以维护和传承。可能的原因有(对前端/测试/数据/平台等研发经历不太了解,这里仅针对一线业务研发):【坚持设计成果而非设计原则】有成功经验的研发同学,倾向于用曾经的架构设计来套用当下的业务场景。这种思路本身没有对错,但如果钉不配锤,往往会在短期内引入大量额外成本,反倒丧失了原本的设计优势。面对具体问题域,只有坚持一贯的设计原则,在需求分析的过程中结合诸多因素进行动态权衡,才能打造真正符合当下和未来需求的设计。【喜欢造新轮子而非持续重构】研发同学的设计原则和代码洁癖可能是一种“玄学”,对前人代码的不待见倒是更具确定性的常态,其实这不难理解。即使都是DDD流派,方案沟通时也未必互相认可;即使经过让步对架构设计达成一致,编码实现的风格也是各领风骚。理解前人的设计思路和代码癖好更重要,还是按时完成业务需求更重要?按自己擅长的设计风格重写更简单,还是在他人的“过时”设计上持续重构优化更简单?【靠文档传承而非工具化复用】对新人来说,文档里的再多建议和快速上手指南,都比不上一个开箱即用的工程DEMO;在成熟应用上持续开发的人,不会因为历史文档上大写的注意事项就抵抗住临时代码换取早点下班的现实诱惑,除非应用工程中有编译/部署失败的强制约束让你不得不放弃。相比于缺少“设计规约”导致的低效协作,已经沉淀的“规约原型”被轻易抛弃更加令人可惜。业务研发的日常工作,本质上是拆解问题域的复杂性,用分层解耦/工具化/平台化/业务抽象的多种思路将子问题逐个击破。如果部分子问题已被很好解决,为何不站在前人肩上?放弃造不造新“轮子”的纠结心态吧,或许我们更需要搭“积木”的心态。【思路】业务架构设计规约结合蚂蚁链-应用技术团队近年来的技术实践,我们试图从自身需求出发,搭建一套或许能满足更多业务场景的业务架构设计规约。重点说明下,本文是从有限的问题域出发提出的解决思路,不奢求成为通用解决方案。如果其他业务线也有类似的痛点,希望能有些许借鉴。【标准】统一业务设计框架,用标准化架构简化技术细节【沉淀】从业务场景中持续沉淀“积木”【重构】持续重构“积木”,减少重复建设【集成】基于业务服务编排引擎快速集成标准——减少细节理想情况下,业务技术只需关注领域建模,但现实中却不得不考虑更多通用的技术细节。以供应链金融场景下简化版的应收账款发行流程为例,需要考虑的有:【领域建模】应收账款领域模型及其行为的设计【流程编排】流程模型的设计及发行流程的状态机设计【数据转换】领域模型<->数据模型及流程模型<->数据模型的双向转换【并发控制】业务锁机制的设计【业务幂等】流程中各业务环节的幂等控制【异常处理】异常捕捉,错误码约定【监控报警】摘要日志,异常日志,边界日志【其他】。。。在以上未完全列举的几项中,除了【领域建模】以外,基本都是与具体业务无关,但对于一个稳定可靠的业务应用不可缺失的部分。如果能建立一套标准化的框架方案,用统一的规范解决掉业务无关的大量细节,是否就能让业务技术同学真正的专注于【领域建模】?沉淀——能力复用沉淀和复用是技术群最常出圈的几个词,可见认同度之高。能力复用不局限于形式和粒度,能够切实降低技术成本,提高业务扩展性,就是不错的沉淀,可作为“积木”供后续使用。以蚂蚁链应用技术团队场景为例,近年来沉淀的能力包括但不局限于:技术类工程规范系列:约束编码规范和边界接口定义风格,日志打印,异常处理,仓储行为,状态机等等读写分离机制:屏蔽交易类需求与查询类需求对数据模型的设计冲突,降低设计复杂性,提升查询性能和灵活性业务类网银核身:提高网银核身签名在不同业务流程中的扩展性合约上链:提高智能合约对接在不同业务流程中的扩展性平台类配置中心:灵活定义和管理业务流程需要的各类配置项产品中心:平台功能打包和隔离,实现业务流程的全局视图重构——持续优化沉淀来源于业务需求,却常常落后于新需求。对于设计者以外的人来说,维护他人的“积木”常常会踩到不少坑,反倒不如自己重写。这也是为何每个团队都在说沉淀,但能够横向复用,甚至在同一个团队内持续复用的能力都少之又少。虽然这个现象没有完美解法,但个人建议采取以积极的心态看待这些“积木”:分析历史背景,了解“积木”出现的技术和业务背景明确该能力能够解决的问题,和不适用的场景分析当下业务需求,是否可以重构该能力后直接复用与创作者沟通,评估重构落地方案这里没有强调重构复用和重写这两种方案的ROI对比,是因为个人看来,即使前者成本更高,重构的过程对个人技术成长和团队文化统一都是有利的。相对于不断推翻和发明新“积木”,持续优化的过程,是对自己和他人经验的不断回顾和反思,看清历史的坑,才能避免新风险的出现。集成——灵活搭建标准能够落地,除了有足够趁手的“积木”库外,更重要的一点,是要有灵活便捷的“粘合剂”,以完成业务功能的快速搭建和灵活调整。在供应链金融的场景下,业务需求主要体现为各种各样的业务流程,比如发行/转让/清分等等。为了简化“积木”搭建,灵活复用底层能力,我们基于以下目标,设计了面向业务的服务编排引擎:标准化:遵循设计规约,将业务无关的通用技术细节屏蔽插件化:对“积木”友好,可持续沉淀和复用新能力业务化:面向业务,有业务描述能力的流程编排配置化:通过配置即可完成流程编排,最好能做到可视化配置产品——业务大图“积木”+“粘合”能够满足技术落地的低成本高扩展,但从业务视角,还需要一个全局大图来描绘业务线的全域能力和功能流程。本文中暂不涉及。【实践】业务架构标准方案如前所说,只靠文档形成的共识,对技术没有足够的约束,极难维持。因此,我们基于上述规约的各项原则,搭建了一套标准化的业务架构设计方案,通过组件化工具化的方式约束业务应用,形成团队共识。一个标准的业务应用架构如下:组件——规范技术细节通过组件化约定,约束通用技术细节的行为,包括但不局限于:交易模型描述业务流程的核心交易模型,用于管控状态推进,维持与业务模型的关联仓储行为数据持久层的通用行为,包括锁定查询/插入/更新/普通查询等事务模板定义事务边界,确保模版内业务逻辑的事务一致性;支持幂等能力通用业务模板定义业务逻辑边界,无事务性保障,但包含了异常处理/日志埋点等通用能力通用查询模板定义查询逻辑边界,与通用业务模板类似,但主要面向单项/批量/分页等查询场景消息对消息中间件的简单封装,适配业务应用规约,降低配置成本调度对调度中间件的简单封装,适配业务应用规约,降低配置成本服务开放api组件规范对外服务能力,通过注解识别服务定义和服务实现,自动生成接口文档,描述接口参数/返回/业务域/错误码等等其他——日志/异常处理/请求参数/返回类型这里不做展开。以上是所有业务应用都会遇到的技术细节,用组件屏蔽细节的思路也没有复杂之处,我们想要表达的重点是:尽可能沉淀和复用技术组件,尽可能使用他人的成果,不要重复搭建,把重心放到业务上!领域——专注业务建模再次强调,对业务技术来说,业务建模是核心,业务建模是核心,业务建模是核心!!!本文的初衷和方案都是为了让开发解放出来,直面核心业务的设计和思考。本小节仅给出领域产出的基本要求,后续在【案例分析】再做详述。领域实体建模的核心是抽象出领域实体及其关联关系,不同的业务场景和设计思路,会有很大差异,最终会体现为一到多个领域模型。需要明确在不同业务流程下,各交易模型内需要包含的领域模型(比较抽象,后续在【案例分析】再做详述)。领域仓储定义数据模型,及其与领域模型的对应关系(各种converter)。基于上文提到的仓储组件,配置数据库表和连接,实现锁定查询/插入/更新/普通查询等业务行为。领域服务基于业务行为,抽象出原子化的领域服务能力。该服务无需关注数据仓储,无需关注业务流程,仅抽象出领域实体的原生能力。以上文中提到的应收账款模型为例,至少需要包含:应收账款的创建应收账款的拆分/流转应收账款的销毁等等基本行为。交易实体用于承载交易流程的业务实体,上文中交易模型的业务实例,内部关联一到多个领域实体。交易仓储用于管控交易实体以及内部各领域实体的仓储行为。服务编排引擎 —— 积木+粘合但凡涉及复杂业务流程的应用,都需要引入流程引擎来编排状态机的流转。业界有很多成熟的流程引擎框架,也有很多足够可用的简化版本。但如前文所说,这类通用引擎的优点也是其最大的弱点:过强的灵活性无法对设计风格形成约束,大量业务细节会分散在不同状态节点间,不直观也难维护。从自身需求出发,我们设计了面向业务的服务编排引擎,以遵循业务设计规约的方式约束技术,以直观的形式描述业务流程。与传统流程引擎相比,其业务友好性主要体现在:约束业务模型:需明确指定业务交易模型/状态及仓储定义,遵循业务设计规范托管仓储行为:只需配置业务模型及仓储实现,无需再关注数据持久化的时机和细节编排领域服务:通过转接层,将领域服务开放到引擎中自由编排。领域的原子能力是引擎编排的最小执行单位灵活增减状态:流程中的状态迁转和业务行为均可被灵活插拔支持“积木”扩展:将可复用的领域服务组合打包,形成固定模式,作为“积木”在其他流程中重复使用总的来说,服务编排引擎基于通用组件屏蔽技术细节,重点专注于业务行为的编排和复用,对“积木”进行“粘合”,以编排出符合业务需求的业务流程。小结本文从业务技术团队的现实痛点出发,尝试以业务架构设计规约(理论标准)结合业务架构标准方案(工程约束)的思路统一团队的技术风格,将技术同学从细节中释放出来,专注于技术能力的积累和对业务价值的思考。希望传达的想法有:用标准约束技术细节:降低业务设计的灵活性,也是为了减少成本用技术工具而非文档推行标准:持续沉淀的组件,胜过没有约束力的文档持续重构而非造新轮子:正视自己和他人曾经的产出,持续改进能带来成长重视业务建模:好好思考业务和行业吧,用DDD武装自己,提升建模思维和能力篇幅所限,【案例分析】后续另开一篇再做详述。文中一些不成熟的想法,也欢迎多多指正。
从单体架构到微服务架构
微服务的优势众多,在现在如果有谁没有听过微服务架构,可以从这里了解一下。本文主要聊一聊是否值得花时间将单体架构重构为微服务架构?微服务架构是一种架构风格,专注于软件研发效能,主要包括单位时间内实现更多功能,或者软件从想法到上线的整个持续交付的过程。在当前的互联网环境中,业务变化迅速,也促使了微服务架构的普及。这种架构迫使团队迅速反应,快速实施,在方案没有过期之前已经上线运行,经受市场考察和考验。目前国内大多数公司正在运行的系统都是单体架构系统,不可否认,这些系统在公司发展过程中,发挥了不可替代的作用,保障了公司正常运行,创造了很多价值。但是,随着系统的日渐膨胀,集成的功能越来越多,开发效率变得越来越低,一个功能从想法到实现,需要花费越来越长的时间。更严重的是,由于代码模块纠结在一起,很多已经老化的架构或者废弃的功能,已经成为新功能的阻碍。众所周知,单体架构随着功能增多,不可避免的是研发效能的降低:研发周期变长、研发资源占用增多。从而引发的情况是:新员工培训时间增多、员工加班时间变长、员工要求涨薪或者跳槽。到了这种情况就说明,单体架构已经不能够满足企业发展需要,这个时候,需要升级架构来提升研发效能,比如微服务架构。想要说明微服务架构的好处,可以来一个比喻。我们建了一个空间站,为此,我们需要将人、货物和设备运输到空间站中,这个时候,运载火箭是表较好的选择,尽管运载火箭造价也比较高,但是几个月发射一次,也能够满足需求。随着空间站的扩大,火箭发射的间隔变短,运输成本高的离谱,而且越来越没法满足空间站运转需求。这个时候,可以尝试另外一种方式,比如,太空电梯。当然太空电梯的造价成本高于一次飞行的费用,但是只要建成,以后的成本就降低了很多。这个比喻也是说明了微服务带来的美好期望,同时也说明一个问题,实施微服务架构会带来巨大的投资。所以,我们在建造太空电梯之前需要想好,我们真的需要这种投入,否则是能是一种浪费。to be or not to be决定从单体架构升级为微服务架构时,先问问自己下面几个问题:产品或系统是否经过市场考验是否需要超过一个团队来保证产品发布系统是否对可靠性、可伸缩性有较高要求微服务架构什么是微服务架构呢?Sam Newman认为是:“一组围绕业务领域建模的、小而自治的、彼此协同工作的服务。”微服务架构中的服务,是根据业务能力抽取的业务模块,独立开发和部署,但是需要彼此配合完成整个业务功能。服务不是单纯的数据存储组件,那是数据库。也不是单纯的逻辑函单元,那是函数。只有同时包括数据+逻辑,才是真正意义上的服务。服务边界服务拆解过程中,DDD(领域驱动设计)可以作为微服务架构的指导方针。因为微服务是围绕业务功能定义服务,根据服务定义团队,这与DDD将业务域拆解为业务子域、定义限定上下文的方法论如出一辙,于是DDD作为微服务的指导方针,快速定义各个服务组件,完成从单体架构到微服务架构的迁移。Alberto Brandolini提出识别服务上下文的方式叫做“Event Storming”。第一步是识别业务域中发生的事件,也就是说,我们的关注点是行为,不是数据结构。这样做的好处是,系统中不同服务之间是松散耦合关系,而且单个服务能够自治。定义好了服务边间,还需要定义事务边界。过去,我们的服务在一个进程中,后面挂着一个数据库,事务可以选择强一致性事务,也就是ACID。当服务增多,彼此配合,这个时候可以使用最终一致性事务,也就是BASE。不同于ACID,BASE更加灵活,只要数据在最终能够保持一致就可以了。这个最终的时间范围,根据不同的业务场景不同,可能是分钟、小时、天,甚至是周或者月。准备工作微服务架构愿景美好,属于重型武器,优点众多,缺点也很明显。服务增多,运维难度增大,错误调试难度增大。所以需要自动化构建、配置、测试和部署,需要日志收集、指标监控、调用链监控等工具,也就是需要DevOps实践。实现DevOps的三步工作法中说明了实现DevOps文化的三个步骤。除了上面提到的基础,还需要在早期确定服务之间如何集成和彼此调用方式,还需要确定数据体系,包括事务一致性和数据可靠性方法。随着服务增多,还需要配置管理、服务发现等众多组件。具体需要的基础组件可以参考微服务的基建工作。这些基础的服务和设计,最好在早起定义,否则,后期需要花费更多的资源才能够完善架构。如果前期缺失,后期也没有补足,造成的后果就是微服务架构迁移失败,最后的系统也只是披着微服务外衣的单体架构。进化还是革命?定义好服务边界之后,还有一个问题需要解决:是逐步进化更新系统、还是破釜沉舟重构整个系统。第二种方式很诱人,比较符合大多数程序猿的思维,系统不行,推倒重来,名为重构。但是在大多数情况下,这种方式不能被允许,因为市场变化迅速、竞争激烈,大多数公司不会停止业务,去等待重构一个能够运行、只是有些缺点的系统。所以,逐步提取更新系统才是王道,大多数公司也能接受。这种方式又被称为绞杀模式。Transformation该如何逐步过渡到微服务架构?下面一步步进行展示:第一步,将用户视图层与服务层部分逻辑进行分离。业务逻辑委托给服务层,支持页面展示的查询定向到数据库。这个阶段,我们不修改数据库本身。第二步,用户视图层与数据库完全分离,依赖于服务层操作数据库。第三步,将用户视图层与服务层拆分为不同服务,并在服务层创建一个API层,用户视图层与服务层之间通信。第四步,拆分数据库,将不同业务数据拆分到不同的数据库中,同时对应业务服务层拆分到不同的服务。用户视图层通过API网关与不同业务服务层的API组件通信。这个时候需要注意,如果团队没有微服务开发经验,可以首先抽取简单业务域服务,因为业务简单,实现简单,可以练手,积累经验。最后一步,拆分用户视图层。绞杀模式的优势就在于,我们可以随着业务变化随时调整方案,不会造成整个业务进化过程的停摆。成功标准当我们完成了整个升级过程,就需要检查一下我们是否达到了预期的结果。引入微服务的目的首先是改善开发流程,我们可以通过简单的指标来衡量:开发周期:从概念到上线持续的时间开发效能:单位时间内团队或个人完成的功能或用户故事系统可伸缩性平均维修时间:查找和排除故障所需时间通过对比老架构和新架构的这些特性值,可以评估升级过程取得的效果。当然,升级过程中也要有这些指标的监控。最重要的事作为攻城狮,我们为能够解决或改善周围世界而自豪,着迷于提供解决方案。同时,我们也要意识到,我们付出的每一份努力,都要有回报。如果不能带来任何回报的重构升级,都是浪费时间。
关于中台建设的理解及建设策略
中台,将通用的、可复用的业务能力沉淀到中台,实现企业级能力的复用。如下图:中台定义(共享、联通、融合和创新)阿里中台定义:中台是一个基础的理念和架构,我们要用中台的思想建设、联通所有基础服务,共同支持上端的业务。业务中台更多的是支持在线业务,数据中台则提供基础数据处理能力和很多的数据产品供所有业务方使用。即由业务中台、数据中台、算法中台等一起提供对上层业务的支撑。ThoughtWorks中台定义:中台是企业级能力复用平台。中台是一种企业级能力,它解决企业的能力共享、业务联通和融合的问题,提供一套企业级的整体解决方案。联通是前台以及中台之间各业务板块的联通,融合是前台企业级业务流程和数据的融合,并以共享的方式支持前台一线业务的发展和创新。中台来源于平台,但与平台不同,主要体现在三个关键能力上:(1)对前台业务的快速响应能力。(2)企业级的复用能力。(3)从前台、中台到后台的设计、研发、页面操作、流程、服务和数据无缝联通、融合能力。“业务+数据”双中台建设模式业务中台包括通用能力中台和核心能力中台。通用能力中台实现对通用能力的沉淀、共享和复用(DDD支撑域或通用域);核心能力中台实现对核心业务能力的共享和复用。数据中台解决微服务拆分后的数据孤岛、数据融合和业务创新等问题。企业中台业务能力建设一般会经历“分”和“合”两个过程。“分”的主要目标是通过业务领域边界划分和微服务拆分,建立稳定的、单一职能的领域模型,实现业务能力的复用和高内聚,让业务具有更强的扩展和复用能力及稳定性。“合”是将拆分后的、稳定的、可复用的核心领域能力进行组合、编排和融合,形成企业级能力,从而灵活快速地适配外中业务和流程以及商业模式的变化。包括业务融合和数据融合。业务融合作用在前台,实现企业不同业务板块能力的联通、组装和整合,实现企业级业务流程的融合,提供一致的前台用户体验。数据融合作用在数据中台,实现企业不同业务板块数据的汇集、集成、分析和商业模式创新等,为企业前台业务提供统一的智能化数据服务。前中后台协同传统企业早期的各个系统都有自己的前端界面和后端业务逻辑,不同系统之间相互独立。如图:烟囱式的系统建设模式所示前台建设需要实现各种不同中台的前端操作,流程和界面的组合、联通和融合。不管后端有多少个中台,前端用户感受到的始终只有一个前台。业务中台建设采用DDD方法,通过领域建模,将可复用的公共能力从各个单体中剥离、沉淀并组合;采用微服务架构,建设成为可共享的通用能力中台。通用能力中台更注重标准化和抽象能力,面向企业所有业务领域实现能力复用。核心能力中台注重适应不同业务场景和渠道的企业核心能力,发挥企业的核心竞争力优势。业务中台落地后的微服务可向前端、第三方和其他中台提供API服务,实现通用能力和核心能力复用。数据中台的建设在于打通数据孤岛,实现业务和数据融合以及商业模式创新,支持业务中台和前台的精细化运营;主要目标包括以下三点:(1)完成企业全域数据的采集与存储,实现对不同中台数据的集中管理;(2)基于不同主题域或场景对数据进行加工和处理,形成面向不同主题和场景的数据应用;(3)建立数据驱动的运营体系,实现精细的数字化运营。相应地建设数据中台分为三步:(1)实现各业务中台的数据汇集,解决数据孤岛和数据共享问题。(2)实现企业级实时或非实时全维度数据的深度融合、加工和共享。(3)萃取数据价值,支持业务创新,加速从数据转换为业务价值的过程。后台主要面向企业内部运营和后台管理人员。可将一些复杂的管理需求从通用的核心业务链路剥离,通过特定程序入口嵌入前台APP或应用中;中台与后台的数据交互可采用事件驱动的异步化的数据最终一致性模式实现数据复制,减轻中台业务压力。
行业观察 | 老牌财险的重生之路:中华财险数字化转型硕果初成
在中华联合保险集团股份有限公司(下称“中华保险集团”)办公楼的电梯间里,显示屏上会不时闪出两行字:“数字中华,倒计时×××天。”这是中华保险集团正式启动数字化转型战略以来的倒计时记录牌。时间回到半年多之前。2020年6月1日,保险业的关注焦点突然聚于一向比较低调的中华保险集团。这一天,中华保险集团与阿里巴巴集团(NYSE:BABA)在北京签署全面合作协议,阿里巴巴集团旗下阿里云将为中华保险集团旗下的中华联合财产股份保险有限公司(下称“中华财险”),构建新一代全分布式保险核心系统,助力中华财险数字化加速转型。双方在核心系统层面的合作,开启了保险行业数字化创新的先河;近7亿元的合作金额,成为国内金融云领域迄今为止的第一大单。2020年6月1日,中华财险与阿里云签署全面合作协议,正式启动数字化转型。图源:中华保险当7亿元大单引起的行业喧嚣渐渐平息,中华财险回归静水流深。经过半年多的改造,中华财险发生了什么样的变化?中华财险副总裁王永祥近日首次对外分享转型背后的思考及阶段性成果。据王永祥介绍,截至2020年12月底,中华财险旗下包括车险报价、理赔资源管理、兴农保、农险GIS等17个重要系统已顺利在阿里云金融云上运行。中华财险首次实现了承接海量并发、碎片式保险订单的能力,并且进一步打开了承接和定义场景保险产品和服务的大门。随着与阿里云合作的稳步深入推进,一个全新的数字化的中华财险正在揭开面纱。“觉醒的大象要起舞”,不破不立作为国内唯一冠名“中华”的保险公司,中华财险已有35年历史,根据保险行业数据显示,目前整体份额位居头部财险公司之列。在人们的印象中,中华财险是一家风格偏于保守的老牌传统保险公司。当其数字化转型战略伴随着7亿元大单对外披露时,在业界引起不小的震动,甚至被称作“觉醒的大象要起舞”。近年来,一批头部保险公司纷纷踏上数字化转型之路。鲜少发声的中华财险亦宣示要进行一场彻底的变革。“变革从来都不是突然发生的。”王永祥表示,早在2017年,中华财险便提出“数字中华”战略。但是在哪些领域以及如何进行数字化转型,彼时并没有清晰的思路。2018年底以来,随着中华财险和中华保险集团新领导班子上任,中华财险下一步的发展路径朝哪个方向走,才渐渐清晰。变革背后有着主动的调整,也有客观因素的驱使。2018年和2019年,中华财险与主要竞争对手的保费收入差距只有约1亿元。“我们面对着巨大的市场挑战,市场份额的争夺已到了白热化地步。”王永祥坦言。对于中华财险来说,现有经营理念和信息系统,无法胜任未来的发展模式,已成掣肘。如果只是在旧模式上缝缝补补,很难适应中华财险快速发展的需要。2020年6月1日,中华财险与阿里云签约,正式开启数字化变革之路。中华财险创新研发中心总经理胡岱磊介绍,中华财险的数字化转型规划,将分为三个阶段实施,大致是按每12个月上一个台阶的节奏执行。具体而言,自2020年6月至2021年6月是第一个阶段,主要打基础,重点突破,实现能力的储备。第二个阶段从2021年6月到2022年6月,夯实业务中台及数据中台,达到增能力、自进化的阶段。第三个阶段从 2022年6月到2023年6月,基于数字化的生态体系建设完成。“公司领导层下了破釜沉舟的决心,公司控股股东也非常支持。不破不立,现在数字化转型已成为公司第一优先级的项目。”王永祥表示。“开着飞机换发动机”,重构核心系统既然要改革,就要往深水区进发。在这一次转型中,中华财险决定对延续几十年的核心业务系统“动刀子”,以支撑公司从以保单为中心转向以客户为中心的经营模式。在跟阿里云的合作中,双方决定重构中华财险的核心业务系统,打造新一代全分布式核心系统,并创新性地引入金融云公共平台。据了解,这也是保险业首次基于混合云模式构建核心系统。在改革之前,中华财险从组织架构到应用系统架构、运营模式实质上仍是以保单为中心的经营模式,信息系统的技术基础架构是基于险种产品的烟囱式架构,车险、意外险、农业险等各险种各有一套集中式系统,彼此割裂运行,底层数据处在未打通的状态。但是实践中,这种按照产品线运营的机制已不能适应中华财险下一步变革和发展的需要。王永祥举例介绍:在现实的场景中,很多客户在购买车险时往往投保了意外险等险种,但在出险时,保险公司对该客户的保单信息可能只知其一、不知其二。因此,如何挖掘客户在保险场景中的真实需求,实现对客户的数字化展业、场景化服务、数字营销、数字化风控等数字化服务,为客户提供更多的价值,正是中华财险希望突破之处。在业务增长的同时启动转型,被王永祥形容为“开着飞机换发动机”。整个系统架构的改变,给中华财险带来的变革将是颠覆性的。王永祥表示,数字化转型之后,公司原有的业务模式和运营逻辑亦将随之改变,科技不再只是底层的支撑,而是与保险业务深度融合,与场景深入结合,这同时又能极大地促进内部的管理变革。“业务和科技系统支持可以形成一个有效的内循环,从而产生持续成长的自身动力。”在阿里云新金融事业部总经理刘伟光看来,与传统保险核心技术相比,新一代保险核心科技所形成的科技战斗力会形成巨大的“代差优势”,帮助率先转型的保险企业建立全新的竞争优势。独创“双中台+乐高式组装”模式作为全球前三、中国第一大云服务商,阿里云此前与多家保险公司开展了合作,这次与中华财险的合作,有什么样的不同?阿里云新金融战略客户部高级专家龙小平介绍,阿里云与其他保险公司的项目合作可能只是基于一个点或一个面,与中华财险的合作则是立体式的。“更重要的是,这次深度合作的模式,可能会创下保险企业数字化转型的一个成功行业样本。”据了解,与其他合作模式不同,阿里云与中华财险采用了独特的“双中台”模式,即业务中台和数据中台并行。阿里率先提出了中台理念。在过往实践中,阿里曾经利用数据中台技术让数据产生价值。在保险场景中,淘宝平台上退货运费险等创新险种,也得益于阿里数据中台发挥了重要的作用。中华财险也对中台的建设寄予厚望。王永祥介绍,在业务中台层面,中华财险将所有的保单合同要素拆分成为原子颗粒级模块,然后领域建模,形成客户、标的、合约、服务等模块,并沉淀在业务中台。根据业务需要,届时各种功能模块按照一定的业务逻辑,进行“乐高式”组装,即可成为创新的保险产品,及时推向市场。在数据中台层面,中华财险将所有业务中台产生的生产交易数据,统一沉淀到由内部私有云平台承载的数据中台层面,通过数据智能分析和建模等,对数据再次加工沉淀,然后再反馈到业务前端,动态调整业务策略,实现业务与数据的双轮联动。王永祥介绍,“双中台”的优势在于,未来如果需要开发一些新业务,只需调整“乐高”模块,不需改写底层代码,这既增加了调整的灵活性,又减少了调整成本,可以快速抓住市场机遇,并创新服务模式。按照中华财险的设想,通过打通底层数据,运行创新算法,未来险种之间可以形成服务上的联动。比如,某个地区出现洪水、冰雹等恶劣天气,出现大量车险报案后,中华财险可以马上启动该地区农业保险的理赔方案。通过在线理赔系统,中华财险零接触为客户提供理赔服务。图源:中华保险“从坐等客户报案,到未来我们可以主动提供理赔服务,体现的是中华财险以客户为中心的服务模式的转变。借助于数据智能技术,主动为客户创造更多价值,将会是中华财险的新常态。”王永祥表示。共创未来,多维度促进数字化转型目前,中华财险的数字化转型规划和路线图正在稳步推进中。其中,最为核心的新一代核心系统正在紧锣密鼓地进行研发,2021年6月将上线一个MVP(Minimum Viable Product, 最小可行性产品)版本。虽然只有半年多时间,中华财险已经开始感受到转型带来的变化。转型中负责技术方案具体落地工作的中华保险集团信息技术部总经理陈小虎,在过去的半年里,见证了转型的阶段性成果。陈小虎介绍,公司已有车险报价、理赔资源管理、兴农保、农险GIS等17个重要系统在阿里云金融云上顺利运行,实现了系统承接能力的飞跃。一个最显著的例子是,2020年“双十一”期间,新保单签约出现峰值,中华财险顺利地经受住峰值对系统的考验。此外,按照以往的模式,如果系统需要升级,往往需要停机部署数小时,而现在甚至短则10分钟就可以完成。同时,通过阿里云金融云的灰度发布功能,中华财险可以及时对业务系统进行升级、修复或调整,整个技术架构的敏捷性非常高,对内部管理变革也带来了很大促进作用。“虽然核心系统还在设计和实施的过程中,但是我们已经看到一些可喜的变化,整个公司节奏和效率明显加快了。”王永祥表示。但在中华保险集团兼财险公司董事长徐斌看来,“数字化转型最大的挑战还是人的思维和理念的改变,以及整个组织转型和业务流程的再造,寻找新的商业模式,这些挑战的难度比系统改造可能要大得多。”据了解,为了加快转型,中华财险正在不断学习互联网科技企业的文化、运营和管理模式,并通过输出管理团队参与阿里云大学培训等方式,推动公司全方位地向数字化转型。徐斌董事长期待,中华财险数字化转型完成后,将产生“业务与科技”、“中华与互联网”等“1+1>2”的合力。
重拾面向对象软件设计
作者 | 聂晓龙来源 | 阿里技术公众号你还在用面向对象的语言,写着面向过程的代码吗?一 前言在欧洲文艺复兴时期,一位伟大的数学家天文学家-哥白尼,在当时提出了日心说,驳斥了以地球为宇宙中心的天体思想,由于思想极其超前,直到半个世纪后开普勒伽利略等人经过后期研究,才逐步认可并确立了当时哥白尼思想的先进性。无独有偶,在软件工程领域也上演着同样的故事。半个世纪前 Kristen Nygaard发明了Simula语言,这也是现在被认同的世界上第一个明确实现面向对象编程的语言,他提出了基于类的编程风格,确定了"万物皆对象"这一面向对象理论的"终极思想",但在当时同样未受到认可。Peter Norvig 在 Design Patterns in Dynamic Programming 对此予以了驳斥,并表述我们并不需要什么面向对象。半个世纪后 Robert C.Martin、Bertrand Meyer、Martin Fowler等人,再次印证并升华了面向对象的设计理念。编程思想的演进也不是一蹴而就,但在这一个世纪得到了飞速的发展。二 编程思想的演进从上个世纪五十年代冯·诺依曼创造第一台计算机开始,一直到现在只有短短70年时间,从第一门计算机语言FORTRAN,到现在我们常用的C++,JAVA,PYTHON等,计算机语言的演进速度远超我们所使用的任何一门自然语言。从最早的面向机器,再到面向过程,到演化为现在我们所使用的面向对象。不变的是编程的宗旨,变化的是编程的思想。1 面向机器计算机是01的世界,最早的程序就是通过这种01机器码来控制计算机的,比如0000代表读取,0001代表保存等。理论上这才是世界上最快的语言,无需翻译直接运行。但弊端也很明显,那就是几乎无法维护。运行5毫秒,编程3小时。由于机器码无法维护,人们在此基础上发明了汇编语言,READ代表0000,SAVE代表0001,这样更易理解和维护。虽然汇编在机器码上更可视更直观,但本质上还是一门面向机器的语言,依然还是存在很高的编程成本。2 面向过程面向过程是一种以事件为中心的编程思想,相比于面向机器的编程方式,是一种巨大的进步。我们不用再关注机器指令,而是聚焦于具体的问题。它将一件事情拆分成若干个执行的步骤,然后通过函数实现每一个环节,最终串联起来完成软件设计。流程化的设计让编码更加清晰,相比于机器码或汇编,开发效率得到了极大改善,包括现在仍然有很多场景更适合面向过程来完成。但软件工程最大的成本在于维护,由于面向过程更多聚焦于问题的解决而非领域的设计,代码的重用性与扩展性弊端逐步彰显出来,随着业务逻辑越来越复杂,软件的复杂性也变得越来越不可控。3 面向对象面向对象以分类的方式进行思考和解决问题,面向对象的核心是抽象思维。通过抽象提取共性,通过封装收敛逻辑,通过多态实现扩展。面向对象的思想本质是将数据与行为做结合,数据与行为的载体称之为对象,而对象要负责的是定义职责的边界。面向过程简单快捷,在处理简单的业务系统时,面向对象的效果其实并不如面向过程。但在复杂系统的设计上,通用性的业务流程,个性化的差异点,原子化的功能组件等等,更适合面向对象的编程模式。但面向对象也不是银弹,甚至有些场景用比不用还糟,一切的根源就是抽象。根据 MECE法则 将一个事物进行分类,if else 是软件工程最严谨的分类。我们在设计抽象进行分类时,不一定能抓住最合适的切入点,错误的抽象比没有抽象复杂度更高。里氏替换原则的创始人Barbara Liskov 谈抽象的力量 The Power of Abstraction。三 面向领域设计1 真在“面向对象”吗// 捡入客户到销售私海
public String pick(String salesId, String customerId){
// 校验是否销售角色
Operator operator = dao.find("db_operator", salesId);
if("SALES".equals(operator.getRole())){
return "operator not sales";
}
// 校验销售库容是否已满
int hold = dao.find("sales_hold", salesId);
List<CustomerVo> customers = dao.find("db_sales_customer", salesId);
if(customers.size() >= hold){
return "hold is full";
}
// 校验是否客户可捡入
Opportunity opp = dao.find("db_opportunity", customerId);
if(opp.getOwnerId() != null){
return "can not pick other's customer";
}
// 捡入客户
opp.setOwnerId(salesId);
dao.save(opp);
return "success";
}这是一段CRM领域销售捡入客户的业务代码。这是我们熟悉的Java-面向对象语言,但这是一段面向对象代码吗?完全面向事件,没有封装没有抽象,难以复用不易扩展。相信在我们代码库,这样的代码不在少数。为什么?因为它将成本放到了未来。我们将此称之为“披着面向对象的外衣,干着面向过程的勾当。” 在系统设计的早期,业务规则不复杂,逻辑复用与扩展体现得也并不强烈,而面向过程的代码在支撑这些相对简单的业务场景是非常容易的。但软件工程最大的成本在于维护,当系统足够复杂时,当初那些写起来最easy的代码,将来就是维护起来最hard的债务。2 领域驱动设计还有一种方式我们也可以这么来写,新增“商机”模型,通过商机来关联客户与销售之间的关系。而商机的归属也分为公海、私海等具体归属场景。商机除了有必要的数据外,还应该收拢一些业务行为,捡入、开放、分发等。通过领域建模,利用面向对象的特性,确定边界、抽象封装、行为收拢,对业务分而治之。当我们业务上说“商机分发到私海”,而我们代码则是“opportunity.pickTo(privateSea)”。这是领域驱动所带来的改变,面向领域设计,面向对象编程,领域模型的抽象就是对现实世界的描述。但这并非一蹴而就的过程,当你只触碰到大象的身板时,你认为这是一扇门,当你触碰到大象的耳朵时,你认为是一片芭蕉。只有我们不断抽象不断重构,我们才能愈发接近业务的真实模型。Use the model as the backbone of a language, Recognize that a change in the language is a change to the model.Then refactor the code, renaming classes, methods, and modules to conform to the new model--- Eric Evans 《Domain-Driven Design Reference》译:使用模型作为语言的支柱,意识到言语的改变就是对模型的改变,然后重构代码,重命名类,方法和模块以符合新模型。3 软件的复杂度这是Martin Flowler在 Patterns of Enterprise Application Architecture 这本书中所提的关于复杂度的观点,他将软件开发分为数据驱动与领域驱动。很多时候开发的方式大家倾向于,拿到需求后看表怎么设计,然后看代码怎么写,这其实也是面向过程的一个表现。在软件初期,这样的方式复杂度是很低的,没有复用没有扩展,一人吃饱全家不饿。但随着业务的发展系统的演进,复杂度会陡增。而一开始通过领域建模方式,以面向对象思维进行软件设计,复杂度的上升可以得到很好的控制。先思考我们领域模型的设计,这是我们业务系统的核心,再逐步外延,到接口到缓存到数据库。但领域的边界,模型的抽象,从刚开始成本是高于数据驱动的。The goal of software architecture is to minimize the human resources required to build and maintain the required system.--- Robert C. Martin 《Clean Architecture》译:软件架构的终极目标是,用最小的人力成本来满足构建和维护该系统的需求如果刚开始我们直接以数据驱动面向过程的流程式代码,可以很轻松的解决问题,并且之后也不会面向更复杂的场景与业务,那这套模式就是最适合这套系统的架构设计。如果我们的系统会随着业务的发展逐渐复杂,每一次的发布都会提升下一次发布的成本,那么我们应该考虑投入必要的成本来面向领域驱动设计。四 抽象的品质抽象永远是软件工程领域最难的命题,因为它没有规则,没有标准,甚至没有对错,只分好坏,只分是否适合。同样一份淘宝商品模型的领域抽象,可以算是业界标杆了,但它并非适合你的系统。那我们该如何驾驭“抽象”呢?UML的创始人Grady booch在 Object Oriented Analysis and Design with Applications 一书中,提到了评判一种抽象的品质可以通过如下5个指标进行测量:耦合性、内聚性、充分性、完整性与基础性。1 耦合性一个模块与另一个模块之间建立起来的关联强度的测量称之为耦合性。一个模块与其他模块高度相关,那它就难以独立得被理解、变化或修改。TCL语言发明者John Ousterhout教授也有同样的观点。我们应该尽可能减少模块间的耦合依赖,从而降低复杂度。Complexity is caused by two things: dependencies and obscurity.--- John Ousterhout 《A Philosophy of Software Design》译:复杂性是由两件事引起的:依赖性和模糊性。但这并不意味着我们就不需要耦合。软件设计是朝着扩展性与复用性发展的,继承天然就是强耦合,但它为我们提供了软件系统的复用能力。如同摩擦力一般,起初以为它阻碍了我们前进的步伐,实则没有摩擦力,我们寸步难行。2 内聚性内聚性与耦合性都是结构化设计中的概念,内聚性测量的是单个模块里,各个元素的的联系程度。高内聚低耦合,是写在教科书里的观点,但我们也并非何时何地都应该盲目追求高内聚。内聚性分为偶然性内聚与功能性内聚。金鱼与消防栓,我们一样可以因为它们都不会吹口哨,将他们抽象在一起,但很明显我们不该这么干,这就是偶然性内聚。最希望出现的内聚是功能性内聚,即一个类或模式的各元素一同工作,提供某种清晰界定的行为。比如我将消防栓、灭火器、探测仪等内聚在一起,他们是都属于消防设施,这是功能性内聚。3 充分性充分性指一个类或模块需要应该记录某个抽象足够多的特征,否则组件将变得不用。比如Set集合类,如果我们只有remove、get却没有add,那这个类一定没法用了,因为它没有形成一个闭环 。不过这种情况相对出现较少,只要当我们真正去使用,完成它的一系列流程操作后,缺失的一些内容是比较容易发现并解决的。4 完整性完整性指类或模块需要记录某个抽象全部有意义的特征。完整性与充分性相对,充分性是模块的最小内涵,完整性则是模块的最大外延。我们走完一个流程,可以清晰得知道我们缺哪些,可以让我们马上补齐抽象的充分性,但可能在另一个场景这些特征就又不够了,我们需要考虑模块还需要具备哪些特征或者他应该还补齐哪些能力。5 基础性充分性、完整性与基础性可以说是3个相互辅助相互制约的原则。基础性指抽象底层表现形式最有效的基础性操作(似乎用自己在解释自己)。比如Set中的add操作,是一个基础性操作,在已经存在add的情况下,我们是否需要一次性添加2个元素的add2操作?很明显我们不需要,因为我们可以通过调用2次add来完成,所以add2并不符合基础性。但我们试想另一个场景,如果要判断一个元素是否在Set集合中,我们是否需要增加一个contains方法。Set已经有foreach、get等操作了,按照基础性理论,我们也可以把所有的元素遍历一遍,然后看该元素是否包含其中。但基础性有一个关键词叫“有效”,虽然我们可以通过一些基础操作进行组合,但它会消耗大量资源或者复杂度,那它也可以作为基础操作的一个候选者。五 软件设计原则抽象的品质可以指导我们抽象与建模,但总归还是不够具象,在此基础上一些更落地更易执行的设计原则涌现出来,最著名的当属面向对象的五大设计原则 S.O.L.I.D。1 开闭原则OCPSoftware entities should be open for extension,but closed for modification-- Bertrand Meyer 《Object Oriented Software Construction》译:软件实体应当对扩展开放,对修改关闭。开闭原则是Bertrand Meyer 1988年在 Object Oriented Software Construction 书中所提到一个观点,软件实体应该对扩展开放对修改关闭。我们来看一个关于开闭原则的例子,需要传进来的用户列表,分类型进行二次排序,我们代码可以这样写。public List<User> sort(List<User> users, Enum type){
if(type == AGE){
// 按年龄排序
users = resortListByAge(users);
}else if(type == NAME){
// 按名称首字母排序
users = resortListByName(users);
}else if(type == NAME){
// 按客户健康分排序
users = resortListByHealth(users);
}
return users;
}上述代码就是一个明显违背开闭原则的例子,当我们需要新增一种类似时,需要修改主流程。由于这些方法都定义在私有函数中,我们哪怕对现有逻辑做调整,我们也需要修改到这份代码文件。还有一种做法,可以实现对扩展开放对修改关闭,JDK的排序其实已经为我们定义了这样的标准。我们将不同的排序方式进行抽象,每种逻辑单独实现,单个调整逻辑不影响其他内容,新增排序方式也无需对已有模块进行调整。2 依赖倒置DIPHigh level modules shouldnot depend upon low level modules.Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions--- Robert C.Martin C++ Report 1996译:高层模块不应该依赖低层模块,两者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。Robert C.Martin是《Clean Code》《Code Architecture》两本经典书籍的作者,1996年他在C++ Report中发表了一篇名为 The Dependency Inversion Principle 的文章。他认为模块间的依赖应该是有序的,高层不应该依赖低层,低层应该依赖高层,抽象不应该依赖细节,细节应该依赖抽象。怎么理解Robert C.Martin的这一观点。我们看这张图,我们的手可以握住这个杯子,是我们依赖杯子吗?有人说我们需要调杯子提供的hold服务,我们才能握住它,所以是我们依赖杯子。但我们再思考一下,棍子我们是不是也可以握,水壶我们也可以握,但猫狗却不行,为什么?因为我们的杯子是按照我们的手型进行设计的,我们定义了一个可握持的holdable接口,杯子依赖我们的需求进行设计。所以是杯子依赖我们,而非我们依赖杯子。依赖倒置原则并非一个新创造的理论,我们生活的很多地方都有在运用。比如一家公司需要设立“法人”,如果这家公司出了问题,监管局就会找公司法人。并非监管局依赖公司提供的法人职位,它可以找到人,而是公司依赖监管局的要求,才设立法人职位。这也是依赖倒置的一种表现。3 其他设计原则这里没有一一将 S.O.L.I.D 一一列举完,大家想了解的可以自行查阅。除了SOLID之外,还有一些其他的设计原则,同样也非常优秀。PLOA最小惊讶原则If a necessary feature has a high astonishment factor, it may be necessary to redesign the feature-- Michael F. Cowlishaw译:如果必要的特征具有较高的惊人因素,则可能需要重新设计该特征。PLOA最小惊讶原则是斯坦福大家计算机教授 Michael F. Cowlishaw 提出的。不管你的代码有“多好”,如果大部分人都对此感到吃惊,或许我们应该重新设计它。JDK中就存在一例违反PLOA原则的案例,我们来看下面这段代码。在分享会上,我故意将这行注释遮盖起来,大家都猜不到 newFormatter.getClass() 这句代码写在这里的作用。如果要检查空指针,完全可以用Objects工具类提供的方法,实现完全一样,但代码表现出来的含义就千差万别了。KISS简单原则Keep it Simple and Stupid-- Robert S. Kaplan译:保持愚蠢,保持简单KISS原则是 Robert S. Kaplan 提出的一个理论,Kaplan并非是一个软件学家,他是平衡积分卡Balanced Scorecard创始人,而他所提出的这个理论对软件行业依然适用。把事情变复杂很简单,把事情变简单很复杂。我们需要尽量让复杂的问题简明化、简单化。六 写在最后软件设计的最大目标,就是降低复杂性,万物不为我所有,但万物皆为我用。引用JDK集合框架创办人Josh Bloch 的一句话来结束。学习编程艺术首先要学会基本的规则,然后才能知道什么时候可以打破这些规则。You should not slavishly follow these rules, but violate them only occasionally and with good reason. Learning the art of programming, like most other disciplines, consists of first learning the rules and then learning when to break them.--- Josh Bloch 《Effective Java》译:你不该盲目的遵从这些规则,应该只在偶尔情况下,有充分理由后才去打破这些规则学习编程艺术首先要学会基本的规则,然后才能知道什么时候可以打破这些规则参阅书籍1、《Object Oriented Analysis and Design with Applications》https://niexiaolong.github.io/Object%20Oriented%20Analysis%20and%20Design%20with%20Applications.pdf2、《Clean Architecture》https://detail.tmall.com/item.htm?id=6543927642493、《A Philosophy of Software Design》https://www.amazon.com/-/zh/dp/173210221X/ref=sr_1_1?qid=1636246895第一期阿里云镜像站体验官持续招募中镜像站体验官第一期招募开始啦, 12月24日前在各大社区平台分享镜像相关内容累计积分就可赢得奖励,银牌体验官的奖励人数不设限哦。点击这里,参与报名吧。
重拾面向对象软件设计
你还在用着面向对象的语言,写着面向过程的代码吗?前言在欧洲文艺复兴时期,一位伟大的数学家天文学家-哥白尼,在当时提出了日心说,驳斥了以地球为宇宙中心的天体思想,由于思想极其超前,直到半个世纪后开普勒伽利略等人经过后期研究,才逐步认可并确立了当时哥白尼思想的先进性。无独有偶,在软件工程领域也上演着同样的故事。半个世纪前 Kristen Nygaard发明了Simula语言,这也是现在被认同的世界上第一个明确实现面向对象编程的语言,他提出了基于类的编程风格,确定了"万物皆对象"这一面向对象理论的"终极思想",但在当时同样未受到认可。Peter Norvig 在 Design Patterns in Dynamic Programming 对此予以了驳斥,并表述我们并不需要什么面向对象。半个世纪后 Robert C.Martin、Bertrand Meyer、Martin Fowler等人,再次印证并升华了面向对象的设计理念。编程思想的演进也不是一蹴而就,但在这一个世纪得到了飞速的发展。编程思想的演进从上个世纪五十年代冯·诺依曼创造第一台计算机开始,一直到现在只有短短70年时间,从第一门计算机语言FORTRAN,到现在我们常用的C++,JAVA,PYTHON等,计算机语言的演进速度远超我们所使用的任何一门自然语言。从最早的面向机器,再到面向过程,到演化为现在我们所使用的面向对象。不变得是编程的宗旨,变化得是编程的思想。面向机器计算机是01的世界,最早的程序就是通过这种01机器码来控制计算机的,比如0000代表读取,0001代表保存等。理论上这才是世界上最快的语言,无需翻译直接运行。但弊端也很明显,那就是几乎无法维护。运行5毫秒,编程3小时。由于机器码无法维护,人们在此基础上发明了汇编语言,READ代表0000,SAVE代表0001,这样更易理解和维护。虽然汇编在机器码上更可视更直观,但本质上还是一门面向机器的语言,依然还是存在很高的编程成本。面向过程面向过程是一种以事件为中心的编程思想,相比于面向机器的编程方式,是一种巨大的进步。我们不用再关注机器指令,而是聚焦于具体的问题。它将一件事情拆分成若干个执行的步骤,然后通过函数实现每一个环节,最终串联起来完成软件设计。流程化的设计让编码更加清晰,相比于机器码或汇编,开发效率得到了极大改善,包括现在仍然有很多场景更适合面向过程来完成。但软件工程最大的成本在于维护,由于面向过程更多聚焦于问题的解决而非领域的设计,代码的重用性与扩展性弊端逐步彰显出来,随着业务逻辑越来越复杂,软件的复杂性也变得越来越不可控。面向对象面向对象以分类的方式进行思考和解决问题,面向对象的核心是抽象思维。通过抽象提取共性,通过封装收敛逻辑,通过多态实现扩展。面向对象的思想本质是将数据与行为做结合,数据与行为的载体称之为对象,而对象要负责得是定义职责的边界。面向过程简单快捷,在处理简单的业务系统时,面向对象的效果其实并不如面向过程。但在复杂系统的设计上,通用性的业务流程,个性化的差异点,原子化的功能组件等等,更适合面向对象的编程模式。但面向对象也不是银弹,甚至有些场景用比不用还糟,一切的根源就是抽象。根据 MECE法则 将一个事物进行分类,if else 是软件工程最严谨的分类。我们在设计抽象进行分类时,不一定能抓住最合适的切入点,错误的抽象比没有抽象复杂度更高。里氏替换原则的创始人Barbara Liskov 谈抽象的力量 The Power of Abstraction面向领域设计真在“面向对象”吗// 捡入客户到销售私海
public String pick(String salesId, String customerId){
// 校验是否销售角色
Operator operator = dao.find("db_operator", salesId);
if("SALES".equals(operator.getRole())){
return "operator not sales";
}
// 校验销售库容是否已满
int hold = dao.find("sales_hold", salesId);
List<CustomerVo> customers = dao.find("db_sales_customer", salesId);
if(customers.size() >= hold){
return "hold is full";
}
// 校验是否客户可捡入
Opportunity opp = dao.find("db_opportunity", customerId);
if(opp.getOwnerId() != null){
return "can not pick other's customer";
}
// 捡入客户
opp.setOwnerId(salesId);
dao.save(opp);
return "success";
}这是一段CRM领域销售捡入客户的业务代码。这是我们熟悉的Java-面向对象语言,但这是一段面向对象代码吗?完全面向事件,没有封装没有抽象,难以复用不易扩展。相信在我们代码库,这样的代码不在少数。为什么?因为它将成本放到了未来。我们将此称之为“披着面向对象的外衣,干着面向过程的勾当。” 在系统设计的早期,业务规则不复杂,逻辑复用与扩展体现得也并不强烈,而面向过程的代码在支撑这些相对简单的业务场景是非常容易的。但软件工程最大的成本在于维护,当系统足够复杂时,当初那些写起来最easy的代码,将来就是维护起来最hard的债务。领域驱动设计还有一种方式我们也可以这么来写,新增“商机”模型,通过商机来关联客户与销售之间的关系。而商机的归属也分为公海、私海等具体归属场景。商机除了有必要的数据外,还应该收拢一些业务行为,捡入、开放、分发等。通过领域建模,利用面向对象的特性,确定边界、抽象封装、行为收拢,对业务分而治之。当我们业务上说“商机分发到私海”,而我们代码则是“opportunity.pickTo(privateSea)”。这是领域驱动所带来的改变,面向领域设计,面向对象编程,领域模型的抽象就是对现实世界的描述。但这并非一蹴而就的过程,当你只触碰到大象的身板时,你认为这是一扇门,当你触碰到大象的耳朵时,你认为是一片芭蕉。只有我们不断抽象不断重构,我们才能愈发接近业务的真实模型。Use the model as the backbone of a language, Recognize that a change in the language is a change to the model.Then refactor the code, renaming classes, methods, and modules to conform to the new model--- Eric Evans 《Domain-Driven Design Reference》译:使用模型作为语言的支柱,意识到言语的改变就是对模型的改变,然后重构代码,重命名类,方法和模块以符合新模型。软件的复杂度这是Martin Flowler在 Patterns of Enterprise Application Architecture 这本书中所提的关于复杂度的观点,他将软件开发分为数据驱动与领域驱动。很多时候开发的方式大家倾向于,拿到需求后看表怎么设计,然后看代码怎么写,这其实也是面向过程的一个表现。在软件初期,这样的方式复杂度是很低的,没有复用没有扩展,一人吃饱全家不饿。但随着业务的发展系统的演进,复杂度会陡增。而一开始通过领域建模方式,以面向对象思维进行软件设计,复杂度的上升可以得到很好的控制。先思考我们领域模型的设计,这是我们业务系统的核心,再逐步外延,到接口到缓存到数据库。但领域的边界,模型的抽象,从刚开始成本是高于数据驱动的。The goal of software architecture is to minimize the human resources required to build and maintain the required system.--- Robert C. Martin 《Clean Architecture》译:软件架构的终极目标是,用最小的人力成本来满足构建和维护该系统的需求如果刚开始我们直接以数据驱动面向过程的流程式代码,可以很轻松的解决问题,并且之后也不会面向更复杂的场景与业务,那这套模式就是最适合这套系统的架构设计。如果我们的系统会随着业务的发展逐渐复杂,每一次的发布都会提升下一次发布的成本,那么我们应该考虑投入必要的成本来面向领域驱动设计。抽象的品质抽象永远是软件工程领域最难的命题,因为它没有规则,没有标准,甚至没有对错,只分好坏,只分是否适合。同样一份淘宝商品模型的领域抽象,可以算是业界标杆了,但它并非适合你的系统。那我们该如何驾驭“抽象”呢?UML的创始人Grady booch在 Object Oriented Analysis and Design with Applications 一书中,提到了评判一种抽象的品质可以通过如下5个指标进行测量:耦合性、内聚性、充分性、完整性与基础性。耦合性一个模块与另一个模块之间建立起来的关联强度的测量称之为耦合性。一个模块与其他模块高度相关,那它就难以独立得被理解、变化或修改。TCL语言发明者John Ousterhout教授也有同样的观点。我们应该尽可能减少模块间的耦合依赖,从而降低复杂度。Complexity is caused by two things: dependencies and obscurity. --- John Ousterhout 《A Philosophy of Software Design》译:复杂性是由两件事引起的:依赖性和模糊性。但这并不意味着我们就不需要耦合。软件设计是朝着扩展性与复用性发展的,继承天然就是强耦合,但它为我们提供了软件系统的复用能力。如同摩擦力一般,起初以为它阻碍了我们前进的步伐,实则没有摩擦力,我们寸步难行。内聚性内聚性与耦合性都是结构化设计中的概念,内聚性测量得是单个模块里,各个元素的的联系程度。高内聚低耦合,是写在教科书里的观点,但我们也并非何时何地都应该盲目追求高内聚。内聚性分为偶然性内聚与功能性内聚。金鱼与消防栓,我们一样可以因为它们都不会吹口哨,将他们抽象在一起,但很明显我们不该这么干,这就是偶然性内聚。最希望出现的内聚是功能性内聚,即一个类或模式的各元素一同工作,提供某种清晰界定的行为。比如我将消防栓、灭火器、探测仪等内聚在一起,他们是都属于消防设施,这是功能性内聚。充分性充分性指一个类或模块需要应该记录某个抽象足够多的特征,否则组件将变得不用。比如Set集合类,如果我们只有remove、get却没有add,那这个类一定没法用了,因为它没有形成一个闭环 。不过这种情况相对出现较少,只要当我们真正去使用,完成它的一系列流程操作后,缺失的一些内容是比较容易发现并解决的。完整性完整性指类或模块需要记录某个抽象全部有意义的特征。完整性与充分性相对,充分性是模块的最小内涵,完整性则是模块的最大外延。我们走完一个流程,可以清晰得知道我们缺哪些,可以让我们马上补齐抽象的充分性,但可能在另一个场景这些特征就又不够了,我们需要考虑模块还需要具备哪些特征或者他应该还补齐哪些能力。基础性充分性、完整性与基础性可以说是3个相互辅助相互制约的原则。基础性指抽象底层表现形式最有效的基础性操作(似乎用自己在解释自己)。比如Set中的add操作,是一个基础性操作,在已经存在add的情况下,我们是否需要一次性添加2个元素的add2操作?很明显我们不需要,因为我们可以通过调用2次add来完成,所以add2并不符合基础性。但我们试想另一个场景,如果要判断一个元素是否在Set集合中,我们是否需要增加一个contains方法。Set已经有foreach、get等操作了,按照基础性理论,我们也可以把所有的元素遍历一遍,然后看该元素是否包含其中。但基础性有一个关键词叫“有效”,虽然我们可以通过一些基础操作进行组合,但它会消耗大量资源或者复杂度,那它也可以作为基础操作的一个候选者。软件设计原则抽象的品质可以指导我们抽象与建模,但总归还是不够具象,在此基础上一些更落地更易执行的设计原则涌现出来,最著名的当属面向对象的五大设计原则 S.O.L.I.D)。开闭原则OCPSoftware entities should be open for extension,but closed for modification-- Bertrand Meyer 《Object Oriented Software Construction》译:软件实体应当对扩展开放,对修改关闭。开闭原则是Bertrand Meyer 1988年在 Object Oriented Software Construction 书中所提到一个观点,软件实体应该对扩展开放对修改关闭。我们来看一个关于开闭原则的例子,需要传进来的用户列表,分类型进行二次排序,我们代码可以这样写。public List<User> sort(List<User> users, Enum type){
if(type == AGE){
// 按年龄排序
users = resortListByAge(users);
}else if(type == NAME){
// 按名称首字母排序
users = resortListByName(users);
}else if(type == NAME){
// 按客户健康分排序
users = resortListByHealth(users);
}
return users;
}上述代码就是一个明显违背开闭原则的例子,当我们需要新增一种类似时,需要修改主流程。由于这些方法都定义在私有函数中,我们哪怕对现有逻辑做调整,我们也需要修改到这份代码文件。还有一种做法,可以实现对扩展开放对修改关闭,JDK的排序其实已经为我们定义了这样的标准。我们将不同的排序方式进行抽象,每种逻辑单独实现,单个调整逻辑不影响其他内容,新增排序方式也无需对已有模块进行调整。依赖倒置DIPHigh level modules shouldnot depend upon low level modules.Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions--- Robert C.Martin C++ Report 1996 译:高层模块不应该依赖低层模块,两者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。Robert C.Martin是《Clean Code》《Code Architecture》两本经典书籍的作者,1996年他在C++ Report中发表了一篇名为 The Dependency Inversion Principle 的文章。他认为模块间的依赖应该是有序的,高层不应该依赖低层,低层应该依赖高层,抽象不应该依赖细节,细节应该依赖抽象。怎么理解Robert C.Martin的这一观点。我们看这张图,我们的手可以握住这个杯子,是我们依赖杯子吗?有人说我们需要调杯子提供的hold服务,我们才能握住它,所以是我们依赖杯子。但我们再思考一下,棍子我们是不是也可以握,水壶我们也可以握,但猫狗却不行,为什么?因为我们的杯子是按照我们的手型进行设计的,我们定义了一个可握持的holdable接口,杯子依赖我们的需求进行设计。所以是杯子依赖我们,而非我们依赖杯子。依赖倒置原则并非一个新创造的理论,我们生活的很多地方都有在运用。比如一家公司需要设立“法人”,如果这家公司出了问题,监管局就会找公司法人。并非监管局依赖公司提供的法人职位,它可以找到人,而是公司依赖监管局的要求,才设立法人职位。这也是依赖倒置的一种表现。其他设计原则这里没有一一将 S.O.L.I.D 一一列举完,大家想了解的可以自行查阅。除了SOLID之外,还有一些其他的设计原则,同样也非常优秀。PLOA最小惊讶原则If a necessary feature has a high astonishment factor, it may be necessary to redesign the feature-- Michael F. Cowlishaw 译:如果必要的特征具有较高的惊人因素,则可能需要重新设计该特征。PLOA最小惊讶原则是斯坦福大家计算机教授 Michael F. Cowlishaw 提出的。不管你的代码有“多好”,如果大部分人都对此感到吃惊,或许我们应该重新设计它。JDK中就存在一例违反PLOA原则的案例,我们来看下面这段代码。/**
* Set a <tt>Formatter</tt>. This <tt>Formatter</tt> will be used
* to format <tt>LogRecords</tt> for this <tt>Handler</tt>.
* <p>
* Some <tt>Handlers</tt> may not use <tt>Formatters</tt>, in
* which case the <tt>Formatter</tt> will be remembered, but not used.
* <p>
* @param newFormatter the <tt>Formatter</tt> to use (may not be null)
* @exception SecurityException if a security manager exists and if
* the caller does not have <tt>LoggingPermission("control")</tt>.
*/
public synchronized void setFormatter(Formatter newFormatter) throws SecurityException {
checkPermission();
// Check for a null pointer:
newFormatter.getClass();
formatter = newFormatter;
}在分享会上,我故意将这行注释遮盖起来,大家都猜不到 newFormatter.getClass() 这句代码写在这里的作用。如果要检查空指针,完全可以用Objects工具类提供的方法,实现完全一样,但代码表现出来的含义就千差万别了。public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}KISS简单原则Keep it Simple and Stupid-- Robert S. Kaplan 译:保持愚蠢,保持简单KISS原则是 Robert S. Kaplan 提出的一个理论,Kaplan并非是一个软件学家,他是平衡积分卡Balanced Scorecard创始人,而他所提出的这个理论对软件行业依然适用。把事情变复杂很简单,把事情变简单很复杂。我们需要尽量让复杂的问题简明化、简单化。写在最后有幸与《代码精进之路》的作者 Frank 之前共事过一段时间,团队内当时我们做DDD,但那时我们对DDD还并不了解。按自己的思路设计,Frank会说书上是这样设计的,当我们严格按照书上的方法设计时,他会说不要教条。有近水楼台先得月,同样也有兔子不吃窝边草,世界本就是两面。软件设计的最大目标,就是降低复杂性,万物不为我所有,但万物皆为我用。引用JDK集合框架创办人、Google首席Java架构师 Josh Bloch 的一句话来结束。学习编程艺术首先要学会基本的规则,然后才能知道什么时候可以打破这些规则。You should not slavishly follow these rules, but violate them only occasionally and with good reason. Learning the art of programming, like most other disciplines, consists of first learning the rules and then learning when to break them.-- Josh Bloch 《Effective Java》译:你不该盲目的遵从这些规则,应该只在偶尔情况下,有充分理由后才去打破这些规则学习编程艺术首先要学会基本的规则,然后才能知道什么时候可以打破这些规则参阅书籍《Object Oriented Analysis and Design with Applications》 《Clean Architecture》 《A Philosophy of Software Design》
成熟的项目架构设计是什么样的?
有网友花了两个月时间做了一个 b2c 商城,技术栈是 sass、jquery、thinkphp,一套摸索下来后,遇到非常多的问题。例如:对项目开发流程等没概念、不知道去哪里查找相关资料。因此他提出来几个问题:项目开发流程的基本组成部分有哪些?如何在初期确定项目整体的运作模型?如何设计数据模型?以下是这位网友在项目初期的一些脑图,由于设计问题,其中退货、优惠券、折扣部分最终并未完成:针对这些问题,淘系技术架构师勇剑同学写出本文一一解答。项目开发流程的基本组成部分有哪些?对于项目开发流程,我的理解,与这位网友实际上大致上是差不多的,不过一般团队里面,都会有业务、产品、服务端、前端、客户端、测试等角色,所以相对整体的沟通成本会高很多,整体的流程大致上是:需求评审、技术方案评审/项目排期、测试用例评审、开发、测试、功能预演、发布上线、线上灰度/放量。实际上这位网友所提到的 “设计整体业务流程” 这一步应该是在技术介入之前就要由产品与业务提前沟通确认好的。“设计数据模型” 应该归属到技术方案这一步里面,是其中的一部分。整个流程对于技术开发同学来说,重点需要关注需求评审、技术方案设计这两大部分,简单谈一下我的一些看法。关于需求,之前看过一段马斯克的工作理念介绍,觉得非常不错,讲的是需求迭代的过程,建议按照下面五步:制定需求的时候,让你的需求别那么蠢(哈哈,也就是希望需求尽量合理,不要做出来了发现没什么用)尝试删除部件或过程,这一步可以让你知道最核心的部件与过程是什么。优化,只有到了第三步,才是优化,不要第一步就优化,让你的优化集中在必要的部件与过程上,这是很多工程师容易搞错的地方。加速前三部循环的时间,你的动作太慢了,更快一点~,在这前,不要加速最后一步是自动化前三步对于我们在一些业务需求的制定上,可以提供一些比较好的参考,在实际项目过程中,也让我们思考什么是我们的核心需求,把有限的人力集中到我们必须要做的事情上面去,拿到更好的成果。另外一点是关于技术方案设计,我觉的是没有一个固定的模版的,重点是要讲清楚在整个技术设计过程中更需要关注的核心技术问题,包括但不限于技术选型、业务交互时序图、实体状态机、数据建模、应用/物理/业务架构,以及高可用保障、稳定性风险等等。如何在初期确定项目整体的运作模型?即对项目整体的前期设计?这个问题的本质实际上是一个领域建模的问题,回到题主的第一张图,在领域建模的时候,我的理解是,先要理清我们业务的核心实体,也就是领域实体,在这张图里面,我们可以清晰看出三部分:用户、商品、订单(当然也可以更细化到一些实体:购物车、物流单、评价等等,这里只是简单举例),体现在设计上,整个业务的应用架构必然需要三大中心:用户中心、商品中心、订单中心。当模型确认之后,模型自身的行为以及模型之间的关系也就随之确定。题主的这个图,看主要是用来介绍整体一些业务流程,包括:实体、用户行为、逻辑判断等等,对于这些建议是通过“时序图”来描述相对会更好一点,可以相对更加清晰的来描述系统、对象、对象与系统的交互行为。简单画了一下用户与几个系统的关系与交互,可以看出需要哪些系统、这些系统需要哪些能力、用户如何与这些系统之间完成交互。PS:这里画的不太全,忽略了所有异常逻辑的处理以及数据一致性问题,包括在用户建单之后,后续的支付流程、以及后续订单的状态机流程这里都没在详细画了,只是简单给个 Demo。关于“用户中心”这块,注册/登录/登出/修改个人信息 这几大部分维护在用户中心是 OK 的,但是优惠券、订单记录查询、订单评论这几部分放在这里感觉不太合适,优惠券属于营销域,应该是归属于一个营销域的“卡券”(coupon)子系统,由该系统提供根据用户 ID 查询优惠券的能力即可。订单记录及查询维护在“订单中心”会更合适一些,至于“评论”,也可以放到一个单独的子系统里面去。简而言之就是根据业务诉求确定系统的领域模型,再对各个领域进行职责划分、功能拆解,之后就是逐渐完善各个子域了。整个项目的架子搭好之后,后续的功能扩展也会相对比较简单。如何设计数据模型?或者说怎么设计数据表?数据库的设计第一步首先要搞清业务诉求,有时候不同的业务需求会导致数据建模存在较大的差异,这一点是我们需要在做设计之前明确的。之后就是根据业务形态,确定领域实体,比如电商涉及到的商品、订单、用户等等,商品域内又包含SPU、SKU、类目信息等等。题主提到的根据前端功能需求来设计数据模型,对于简单的需求来说,是可以的,但对于复杂业务如果这样设计,后续如果前端功能需求发生变动,那么对整个系统的改造也是灾难性的,建议建立起一套稳健的领域模型,将前端需求与底层存储做好防腐和解耦。在DB层的建模做好适当的抽象、冗余等处理,数据在系统里面通过领域实体流转,对于页面只是一个实体的VO渲染即可。另外数据表的设计,需要考虑各方面因素,比如数据量的预估(是否需要分库分表)、存储方式(存储技术选型)、有没有热点数据、预留未来扩展的可能性等等关于 DB 建模,建议采用 ER 图来设计,数据表的关联关系展现的会相对清晰一些。下面是一个简单的 Demo关于 MySql 数据库方面的技术,推荐一本书:《高性能MySQL》画图工具最后关于画图这块,推荐几款比较好用的作图利器哈:OmniGraffle:https://www.omnigroup.com/omnigraffledraw.io:https://drawio-app.com/examples/PlantUml:https://plantuml.com/zh/sequence-diagram
聊一聊中台和DDD(领域驱动设计)
本次分享价值:本次分享主要针对中台、微服务和领域模型的理念、本质及其构建方法论进行探讨。对领域分析的价值所在就是寻求“千变万化”中相对的“稳定性、第一性”,然后通过合理的架构分析及抽象隔离业务的复杂度和技术复杂度,隔离业务领域的稳定性和易变性,从架构上精巧、快速的支撑业务的变化。#中台到底是什么?中台的概念最早是从阿里流传出来的,阿里的中台战略是从业务中台和数据中台开始的,采用业务中台和数据中台相结合的双中台建设模式。后来也有人提出技术中台,AI中台等等。在阿里完美落地中台之后,很多企业开始对阿里对标。完成大一统的集中式系统拆分,实现了从传统应用向大平台的演进。但是每个企业对中台的理解,也都不一样,**那么阿里的中台到底是什么样的?**阿里业务中台的前身是共享平台[https://cloud.tencent.com/developer/article/1780514],而原来的共享平台更多的是被当作资源团队,承接各业务方的需求,并为业务方在基础服务上做定制开发。阿里业务中台的目标是把核心服务链路(会员、商品、交易、营销、店铺、资金结算等)整体当做一个平台产品来做,为前端业务提供的是业务解决方案,而不是独立的系统。这种能力有别于传统的烟囱的系统建设方式。**说到这里,那么中台到底是什么呢?**其实不同人不同团队对于中台的定位和理解是存在很大争议的。我们先看下阿里对中台的定义:中台是一个基础的理念和架构,我们要用中台的思想建设、联通所有基础服务,共同支持上端的业务。业务中台更多的是支持在线业务,数据中台则提供基础数据处理能力和很多的数据产品供所有业务方使用。即业务中台、数据中台和算法中台等一起提供对上层业务的支撑。我们可以看到关于中台的几个关键词:共享、联通、融合和创新。联通是前台和中台之间各业务版本的联通。融合是前台企业级业务流程和数据的融合,并以共享的方式支持前台一线业务的发展和创新。其实我对中台的理解就是,中台首先体现的是一种企业级的能力,他提供的是一套企业级的整体解决方案,解决小到企业、集团,大到生态圈的能力共享、业务联通和融合的问题,支持业务和商业模式的创新。通过平台联通、业务和数据融合,为前台用户提供一致体验,更敏捷的职称前台一线业务。中台来源平台,但于平台相比,中台更多的是一种理念的转变,它主要体现在这三个关键的能力上。1、对前台业务的快速响应能力。2、企业级的复用能力。3、从前台、中台到后台的设计、研发、页面操作、流程、服务和数据的无缝联通、融合的能力。#微服务设计为什么要选择DDD?其实最近几年微服务架构的思想越来越普及,很多企业已经或者尝试从单体架构向微服务架构转型。微服务也成为很多中大型企业实施中台战略的不二之选。但是在微服务实施过程中有很多问题,单体应用到底应该如何去拆分微服务?边界到底怎么划分?微服务这个微字到底如何衡量,到底拆成到什么粒度合适?微服务应该如何设计?对于这类问题,不同团队不同人都有自己的经验和对微服务的理解,各执一词,那么有没有适合的理论或者设计方法来知道微服务设计呢?## 软件架构的演进史软件的架构模式大体来说经历了从单机、集中式到分布式微服务架构三个阶段的演进。第一阶段:单机架构,这个阶段通常采用面向过程的设计方法,通常采用C/S架构,大多采用结构化编程方式,第二阶段:集中式架构,这个阶段通常采用面向对象的设计方法。一般采用经典的三层架构,系统包括业务接入层、业务逻辑层和数据库层。这种设计模式往往容易使系统变得臃肿,可扩展性和弹性伸缩能力差。第三阶段:分布式微服务架构,该架构可以是实现业务和应用之间的解耦,解决集中式单体应用扩展性和弹性伸缩能力不足的问题,更加适合云计算环境下的部署和运营。##微服务拆分和设计的困境微服务架构的引入,的确解决了单体应用的很多问题,比如扩展性、弹性伸缩能力、小规模团队的敏捷开发等。但是在微服务实践过程中,也产生了不少争论和疑惑,比如微服务的粒度如何把握?微服务到底如何拆分和设计呢?微服务的边界到底应该在哪里?可以说,很久以来都没有一套系统的理论和方法来知道微服务的拆分和设计。微服务拆分困境产生的根本原因,就是不知道业务或者应用的边界到底在什么地方。换句话说,如果确定了业务边界和应用边界,这个困境也就迎刃而解的。其实有时候微服务设计的重点不在于微服务的大小,也不在于拆分了多少个微服务,而是在于微服务内部的边界是否清晰,这些边界是否进行了有效的隔离,以及这些微服务上线后能否随着业务的发展轻松实现业务模型和微服务架构的演进,所以,在微服务设计时,我们要考虑微服务拆分的大小,也要关注微服务内部的逻辑边界。微服务设计强调从业务领域出发,因此我们第一步要做的就是先划分业务的领域边界,然后在这个边界内构建业务的领域模型,根据领域模型来完成从单体应用到微服务的建设。那么如何确定业务和应用的边界?是否有理论或知识体系来知道呢?那就是DDD!DDD也就是领域驱动设计,2003年Eric·Evans出版了《领域驱动设计》这本书之后,DDD诞生。DDD的核心思想就是从业务角度出发,根据限界上下文划分业务的领域边界,定义领域模型,确认业务边界。在微服务落地时,建立业务领域模型和微服务代码模型的映射关系,从而保证业务架构和微服务系统架构的一致性。但DDD提出后在软件开发领域一直都是雷声大,雨点小。直到Martin Fowler提出微服务架构后,DDD才迎来了自己的时代。## 为什么DDD适合微服务首先DDD是一种处理高度复杂领域的设计思想,一种结构设计方法,它试图分离技术实现的复杂性,并围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解,难以演进的问题。而微服务是一种架构风格,两者从本质上都是为了追求软件的高响应力,从业务视角去分离应用系统建设复杂度的手段。两者都强调从业务领域触发,根据业务的发展,合理规划业务领域边界,采用分治策略,降低业务和软件开发的复杂度,持续调整现有架构,优化现有代码,以保持架构和代码的生命力,也就是我们常说的演进式架构。那为什么说DDD适合微服务呢?我们可以通过DDD战略和战术设计方法,划定业务领域边界,构建领域模型,用领域模型指导微服务拆分和设计,解决微服务的业务和应用边界难以划分的难题,同事解决微服务落地时设计的难题。另外,更关键的一点是,DDD不仅可以指导微服务的边界划分和设计,也可以很好地应用于企业中台的领域建模设计,帮你建立一个边界清晰、可高度复用的企业级中台业务模型,完成微服务的落地。那是不是说,只有微服务系统才使用用DDD呢?DDD不仅适用于微服务拆分和设计,同样也适用于单体应用。如果单体应用采用了DDD方法设计,当某一天你想将单体应用拆分为多个微服务时,你会发现采用DDD 方法设计出来的单体应用,拆分起来比采用传统三层架构设计出来的单体应用容易很多。这是因为用DDD方法设计的单体应用,在应用内部会有很多聚合,聚合之间是松耦合的,但聚合内部的功能具有高内聚的特点。有了这一层清晰的聚合边界,我们就可以很容易完成从单体应用向微服务的拆分了。# DDD、中台和微服务的关系DDD和微服务源于西方,而中台来自于阿里,三者看起风马牛不相及,实则缘分匪浅。中台是抽象出来的业务模型,微服务是业务模型的系统实现,DDD作为方法论可以同时知道中台业务建模和微服务建设,三者相辅相成,完美结合。你可能会疑惑,为什么DD都可以同时指导中台和微服务建设呢?这是因为DDD有两把利器,那就是它的战略设计和战术设计方法。中台在企业架构上更多的是偏向业务架构,形成中台的过程实际上也是业务领域不断细化和能力沉淀的过程。在这个过程中我们会对同类通用的业务能力进行聚合和重构,再根据限界上下文和业务内聚的原则建立领域模型。DDD战略设计最擅长的就是领域建模。在中台完成领域建模后,DDD战略设计构建的领域模型就可以作为微服务设计的输入。此时,限界上下文和领域模型可以作为微服务拆分和设计的边界和依据,所以,DDD的战术设计又恰好可以与微服务设计完美无缝结合。可以说,业务中台和微服务正是DDD实战的最佳场景。#DDD的基本原理在DDD的知识体系里有很多概念,比如领域、子域、核心子域、通用子域、支撑子域、限界上下文、聚合、聚合根、实体、值对象、领域服务和应用服务等。他们在DDD理论和知识体系里都是非常重要的概念。## 什么是领域和子域领域,名词解释是“领域是从事一种专门活动或事业的范围、部类或部门”,在DDD中领域就是用来确定范围的,范围即边界。在研究和解决业务问题时,DDD会按照一定的规则对业务领域进行细分,当领域细分到一定的程度后,DDD会将问题范围限定在特定的边界内,在这个边界内建立领域模型,进而用代码实现该领域模型,解决响应的业务问题。简而言之,DDD的领域就是这个边界内要解决的业务问题域。子域,既然领域是用来限定业务边界和范围的,那么就会有大小之分,领域越大,业务边界的范围就越大,泛指则相反。领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或者更小的业务范围。## 什么是限界上下文在DDD领域建模过程中,会有很多项目参与者,不同人对同样的领域知识会有不同的理解。而且有时候同一领域内的名词和术语也可能不统一,团队成员交流起来会出现障碍,严重时甚至会传达错误的信息。在DDD中有“通用语言”和“限界上下文”这两个重要概念。两者相辅相成,通用语言用于定义上下文对象的含义,而限界上下文则用于定于领域边界,以确保每个上下文对象在它特定的边界内具有唯一的定义,在这个边界内,组合这些对象构建领域模型。什么是通用语言:通过团队交流达成共识的,能够简单、清晰、准确地描述业务含义和规则的语言就是通用语言。通用语言中的名词一般可以给领域对象命名,比如货源、订单等,他们对应领域模型中的实体对象。而动词则表示一个动作或领域事件,如果下单,订单支付,它们对应领域模型中的领域事件或者命令。限界上下文:为了避免同样的概念或语义在不同的上下文环境中产生歧义,DDD在战略设计上提出了“限界上下文”这个概念,用来确定语义所以的领域边界。“限界”是指具体的领域边界,而“上下文”则是业务语义所在的上下文环境。定义:限界上下文就是在限定的上下文环境内,用来封装通用语言和领域对象,保证领域内的一些术语、领域对象等有一个确切的含义,没有语义二义性的一个业务边界。举个例子,企业在设置组织架构时,就是在定义企业的限界上下文边界。而且往往会从企业的职能边界出发,根据这些只能边界来设置部门,划定部门的边界,比如:可以为人力资源管理相关设置人力资源部,为财务核算管理相关的职能设置财务部门等等。## 什么是实体和值对象DDD战术设计中有两个重要的概念:实体(Entity)和值对象,两个都是领域模型中非常重要的基础领域对象。实体:在DDD的领域模型中有这样的一类对象,它们拥有唯一标识符,并且他们的标识符在历经各种状态变更后仍能保持一致。对这些对象而言,重要的不是属性,而是其延续性和标识,这种对象的延续性和标识会跨越升值超过软件的生命周期。我们把领域模型中这样的领域对象成为实体。在代码模型中,实体的表现形式就是实体类。这个类包含了实体的属性和方法,通过这些方法实现实体自身的业务行为和业务逻辑。这些实体通常采用充血模型,与实体相关的所有业务逻辑都在实体类方法中实现,跨多个实体的领域逻辑则在领域服务中实现。值对象:相对实体而言,值对象会更加抽象一些,我们来看下《实现驱动领域设计》中对值对象的定义。值对象是通过对象属性值来识别的对象,它将多个相关属性组合成一个概念整体,用于描述领域的某个特定方面,并且是一个没有标识符的对象。也就是说,值对象描述了领域中的某一个东西,这个东西是不可变的,它将不同的关联属性组合成了一个概念整体。举个例子人员实体:包括姓名,年龄,性别及人员所在的省、市、县和街道等属性。这样在人员实体中,显示地址的多个属性就会显得很零碎。所以,我们把“省、市、县和街道”等属性拿出来,构成一个地址的属性集合,这个属性集合的名称就是地址值对象。## 什么是聚合和聚合根在DDD中实体和值对象是很基础的领域对象。但实体和值对象知识个体化的业务对象,他们所表现出来的是个体的行为和能力,在领域模型中我们需要一个这样的组织,将这些紧密关联的个体对先聚集在一起,按照组织内统一的业务规则共同完成特定的业务功能,因此就有了聚合的概念聚合:领域模型内的实体和值对象就类似这些组织中的个体,而能让实体和值对象协同工作的组织就是聚合。换句话说,从技术的角度,聚合是有业务和逻辑紧密关联的实体和值对象组合而成的。聚合根:聚合内有一定的业务规则以确保聚合内数据的一致性,如果在实现业务逻辑时,任由服务对聚合内实体数据进行修改,那么很可能会因为在数据变更过程中失去统一的业务规则控制,而导致聚合内实体之间数据逻辑的不一致。所以引入了聚合根,聚合根的主要目的是避免聚合内由于复杂数据模型缺少统一的业务规则控制,而导致聚合内实体和值对象等领域之间数据不一致的问题。也就是说,在聚合根的方法或领域服务中可以用上这些业务规则,来确保聚合内数据变更时可以保持数据逻辑一致性。如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根就是这个组织的负责人。聚合根也称为根实体,但它不仅是实体,还是聚合的管理者。##什么是领域事件在领域建模时,我们发现除了命令和操作等业务行为以外,还有一类非常重要的事件。这类事件发生后通常会触发进一步的业务操作,在DDD中这类事件被称为领域事件。举例来说,领域事件可以是业务流程的一个步骤,比如下单成功就会去触发下架货源,也可以是定时触发的事件,也可以是一个事件发生后触发的后续动作等等。领域事件采用事件驱动架构设计,可以切断领域模型之间的依赖关系,在领域事件发布后,事件发布方不必关心订阅方的事件处理是否成功。这样就可以实现领域模型的解耦,维护领域模型的独立性。当领域模型映射到微服务时,领域事件就可以解耦微服务,这时微服务之间的数据就可以不再要求强一致,而是基于最终一致性。## 小结聚合、聚合根、实体和值对象是领域层聚合内非常重要的领域对象,他们之间具有很强的关联性。聚合里包括聚合根。实体、值对象和领域服务等,他们共同按照聚合的业务规则完成聚合的核心领域逻辑。总结下他们之间的联系和区别。聚合的特点:聚合内部业务逻辑高内聚,聚合之间满足低耦合的特点。聚合是领域模型中最小的业务逻辑边界。聚合是领域模型中可拆分为微服务的最小单位,可以按照聚合独立拆分成一个微服务,但主要不要过度拆分,这样会增加运维和集成成本。聚合根的特点:聚合根是实体,有实体的特点,拥有全局唯一标识,有独立的生命周期。一个聚合只有一个聚合根,聚合根在聚合内对实体和值对象通过对象引用的方式进行组织和协调,聚合与聚合之间只能通过聚合根ID引用的方式,实现聚合支架的访问和协同。实体的特点:实体有ID标识,通过ID判断相等性,ID在聚合内唯一即可。实体的状态可变,他依附于聚合根,其生命周期由聚合根管理。实体一般会持久化,但与持久化对象不是一对一的关系。实体可以引用聚合内的聚合根、实体和值对象。值对象的特点:值对象没有ID,且数据不可变,他没有生命周期,用完即扔。值对象通过属性值判断相等性。它是一组概念完成的属性组成的集合,用户描述实体的状态和特征,其核心本质是“值”。# DDD的分层架构微服务的架构模型有好多种,有洋葱架构,CQRS和六边形架构等。虽然这些架构模式提出的时代和背景不同,但其核心理念都是为了设计出“高内聚,低耦合”的微服务。DDD分层架构的出现,使微服务的架构边界变得越来越清晰,在微服务架构模型中,占用非常重要的位置,那么到底DDD的分层架构时什么样的?我们看下DDD架构的发展与演进。用户接口层常见解释是这样的,用户接口层负责向yoghurt展示信息的和解析用户指令,这里的用户可能是用户、程序、自动化测试和批处理脚本等。用户接口层主要facade接口、DTO以及DO数据的组装和转换等代码逻辑。主要作用就是避免暴露微服务的业务逻辑,导致数据外泄,不能将后端对象的所有属性数据,不加区分的暴露给所有前端应用。更不能因为前端应用不同的数据展示需求,而在后端定制开发出多个不同的应用服务或领域服务,这样会产生大量重复代码,也容易导致业务逻辑混乱,代码可维护性变差。应用层:连接用户接口层和领域层,他是很薄的一层,主要职能就是协调领域层多个聚合完成服务的组合和编排。但切记不要将本应该在领域层的核心领域逻辑在应用层时间,这会使得领域模型失焦,时间一长就会导致应用层和领域层的边界变得混乱,边界清晰的四层架构满满就可能演变成业务逻辑复杂的三层架构了。领域层:领域层位于应用层之下,是领域模型的核心,主要实现领域模型的核心业务逻辑,体现领域模型的业务能力。领域层用于表达业务概念、业务状态和业务规则,可以通过各种业务规则校验手段保证业务的正确性。领域层主要关注实现领域对象或者聚合自身的原子业务逻辑,不太关注外部用户操作或者流程等方面的业务逻辑。基础层:基础层贯穿了DDD所有层,它的主要职能就是为其他各层提供通用的技术和基础服务,包括第三方服务,驱动,消息中间件,网关,文件,缓存以及数据库等。重要原则:每层只能与其下方的层发生耦合。# 中台领域和微服务建设方法论## 如何用事件风暴构建领域模型前面我们说过微服务设计为什么要选择DDD,最重要的一点就是可以用DDD方法建立的领域模型,清晰地划分微服务逻辑边界和物理边界。那么我们应该采用什么样的方法,去构建领域模型呢?下面就在DDD战略设计中常使用的一种方法:事件风暴事件风暴是在2013年由老外提出来的,事件风暴是一项团队活动,领域专家与项目团队通过头脑风暴的形式,罗列出领域中所有的领域事件、整合之后形成最终的领域事件集合,然后,为每一个事件标注出导致该事件的命令,再为每一个事件标注出命令发起方的角色。命令可以是用户发起,也可以是第三方系统调用或者定时器触发等,最后对事件进行分类,整理出实体、聚合、聚合根以及限界上下文等,在限界上下文边界内构件领域模型。另外事件风暴过程也是建立团队通用语言的过程,这个过程对项目团队确定项目建设目标,完成业务领域模型分析,系统建设和落地非常重要。领域建模的关键过程包括:产品愿景分析、场景分析、领域建模、微服务拆分与设计等几个重要阶段。