当前位置: 首页 > 科技观察

如何形成统一的设计风格-实践

时间:2023-03-15 01:11:26 科技观察

背景在上一篇《业务团队如何统一架构设计风格?》中,讨论了一个业务架构的设计规范,目的是为了实现这些目标:用标准来约束技术细节;使用技术工具而不是文件来推广标准;持续重构而不是新轮子;强调业务建模。但整体表达较为抽象。本文将总结团队近期的架构演进工作,并以更具体的技术细节对概念进行详细阐述,作为“统一业务设计风格”的实战文章。文章详细介绍了多层次的设计规范和基于规范的施工方法,并在文末回答了上一篇文章中的诸多问题。2概述上图以电商产品为例,展示了一个标准框架各层的设计单元。先简单了解一下概念,下一章会详细讲解各层的设计规范和构建方法:通过签名者的身份定位产品功能的适用场景;描述一个独立完整的功能域,组的集合就是产品功能的范围和边界。通过组装合约组,可以快速构建商业产品。在业务模型层,为了减少领域内不同技术同学的建模风格差异,我们对业务模型的使用场景做了很多约定,打通了仓库管理/业务流程/等基础模块系列业务组件。大家更注重业务在模型上的表达,大大减少了对实现细节的关注。基于领域分析,可以快速构建业务模型。业务流程层使用一套标准的业务流程框架来描述业务模型的完整执行过程:业务组件是一组高度内聚的业务功能。该设施执行特定功能;流程引擎负责创建和管理流程实例,接收指令触发组件动作的执行,实现状态推进/条件跳转和异常处理等分支控制需求。通过对业务组件/基础设施的抽象和沉淀,可以快速构建业务流程。数据视图层使用一套标准的数据流机制来满足视图层的定制化需求:数据流订阅者用于收集数据,物理来源包括区块链跨链数据/业务DB数据/文件系统数据/离线任务数据等;数据流消费者用于处理原始数据,生成表现层数据/待查数据/数据指标等,订阅者保证数据源的稳定和低成本的快速访问,消费者交给技术同学自定义业务逻辑。在不干扰领域建模的基础上,可以快速构建数据视图。三大规约详解1ProductModel产品契约1)规约产品契约从全局的角度描述了一个完整的商业模式,包括:服务目标客户、依赖的业务领域、输出的服务等。产品契约的内容是一个staticdescriptionfile,使用场景需要通过签署身份列表来定义2)例子待上架的产品;商户+平台+买家签订的产品合约,适用于交易下单场景。3)新建/修改低代码:根据业务需求,在产品中心设计产品模板,明确合约分组及具体内容使用:接入时编码,一次性:编写产品合约对应的模型以及业务系统中的签名身份类,完成与产品中心的对接,包括合约创建/失效,基于签名身份的合约查询等。合约分组1)规范合约分组描述了一个高内聚业务提供的功能和依赖关系局部视角的域配置信息,包括:业务模型、业务服务、业务流程、业务组件等。多个合约组共同组成一个产品合约,可以交付业务2)示例电商产品合约下,产品组描述流程和配置和订单组约束订单创建流程和服务信息,以及退货组描述买家可以享受的退货流程和客户服务。3)构建新的/修改的低代码:以元数据的形式定义一个契约组,包括模型/流程/配置等。每个配置都可以通过关键路径/配置值类型和限制来描述。使用硬编码:在业务系统中定义合约分组的模型类,完成与产品合约内容交互的写入和读取,并在业务代码处显式获取业务分组实例低代码:构建通用框架对于合约查询->分组分析->配置获取(引入Cache是??为了避免重复查询),业务层只需要通过元数据描述来描述对应分组中的配置信息即可。业务组件的业务实例一个业务模型可以与其他模型相关联,但应避免循环依赖。一个完整的业务模型描述需要包括:数据模型、视图模型、业务模型/数据模型/视图模型的三种转换、业务模型Warehousing等。2)示例退货业务,基于退货订单推动业务流程,各业务组件从退货单中获取必要的业务信息,执行退货/退款/通知等业务功能;退货单与远期订单关联,但远期订单不能反向依赖于退货单;一个退货单模型对应一个主单据表和多个退货明细,仓库需要负责完成业务模型<->数据模型的双向读写3)构建硬编码:编写业务模型(模型)/数据模型(DO)/数据交互(Mapper)/视图模型(VO)/转换层(Converter)/存储(Repository)等低代码:用元数据描述,自动生成DO/VO/Mapper/Converter;基于基础提供的仓储组件,也可以通过元数据描述自动生成业务模型仓储的实例服务1)规范1.业务服务是以一组业务域为单位(接口)进行聚合,开放对所有内外部用户最小的业务功能单元(方法)2.业务服务需要一套定义规范(注解/aop等),每个功能单元都有清晰直观的元数据描述n实现服务发现/文档生成/权限控制/稳定性安全等。元数据包括:业务域、业务动作、读/写、错误码范围、返回值模型等。3、业务服务的输入参数限定为一个sysParam和一个bizParam,前者为调用源/幂等ID/productcode/tenantID等系统参数,后者是各业务定义的模型参数,推荐为POJO4,可以全链路透传(rpc->api->flow->component),业务服务以Result形式返回,错误码尽可能控制在元数据描述的范围内,不向调用方泄露任何异常。返回的业务信息推荐使用POJO或者VO5。业务服务不限于调用者的物理来源。只需要在对接层添加简单的转换逻辑就可以进行授权控制。6.写服务的实现需要事务管理机制2)示例publicinterfaceDemoOrderService{/***Orderapplication*@paramsysParamsysParam*@parambizParambizParam*@returnresult*/@ApiFunction(apiType=ApiType.SUBMIT,funcBiz="ORDER",funcAction="APPLY",returnType=OrderApplyResponse.class,errorCodeType=CommonErrorCodeEnum.class)CommonResultapply(ApiReqSysParamsysParam,OrderApplyInfobizParam);}3)Buildnew/modifieddefinition-lowcode:基于元数据描述,自动生成接口+方法+errorcode+POJO等实现硬编码:简单需求/无法模板化/处理的业务需求,直接编码low-code:用于标准流程发起服务(申请上市/申请下单/申请退货),使用模板实现合约组加载->流程配置加载->流程初始化(幂等)->流程触发->结果处理;对于标准流程推进服务(通知接收/调度推进),通过模板实现流程配置加载->流程触发->结果处理等,随着更多服务场景的出现,可以有更多的模板化业务服务。使用硬编码:与所有接口使用一样,组装请求->调用->处理结果低代码:基于元数据描述和业务配置,将当前业务对象/外部参数映射到服务入参的POJO,模板异常处理,成功返回结果以相同方式映射回业务对象或外部响应流程1)规范1.流程用于描述一个完整的业务流程,基于单一的业务模型,推动一个或多个业务子环节2.对于单一业务模型同一类型的业务流程可以有多个Flow定义,以满足不同业务模型的定制需求。3.Flow包括transition、component、action三层结构。其工作原理如下:触发器(operate)对应于组件的一个动作。动作全部成功的组件会结束,组件全部成功的transition会触发Flow和businessmodel的状态转换。4.Flow的目标是将复杂的流程拆解成多个原子业务动作,并相互解耦。5.需要调用业务服务/消息/调度等入口来触发流程,实现完整的流程推进。6.流程需要依赖外部调用者提供事务管理机制(通常是业务服务),需要依赖业务模型存储来控制模型加载和存储2)实例3)新建/修改低代码:Flow本身的运行由基础组件支持,只需要一次性编码;如果需要定义业务流程,可以动态定义基于业务组件模板和业务模型生成Flow配置文件,配合版本控制和隔离机制,防止兼容性问题使用硬编码:Flow初始化场景,从当前业务领域的合约组中获取需要的Flow配置,流程初始化和推进;流程推进场景,基于modelId+modelType+operate+request,可以使用模板化代码自动触发low-code:通过合约分组中Flow配置的标准化,也可以实现Flow初始化场景模板化的方式;当现有业务服务需要支持时,创建新的自定义业务流程时,只需要调整合约中的配置即可。组件1)规范1.业务组件是某一类业务动作的集合,针对业务功能而设计,不局限于任何业务模型2.业务组件业务动作是最小的原子业务单元。粒度没有强制性要求,但根据解耦和复用的程度而定;建议依赖一个或多个基础设施/业务服务,并以模板化的方式提供标准的服务。业务动作的实现3.对于某种业务模型,业务组件支持通过开放适配器进行受控定制(详见【基础架构-适配】),或者完全重写实现专属定制(不允许其他业务复用)4.所有核心业务逻辑都应该包含在业务组件层及以下(没有流程的简单业务服务除外),包括但不限于:参数验证、业务验证、可重入/幂等控制、业务模型变更、合约分组变更、计算规则、外部服务交互等。5.业务组件需要一套定义规范(xml/注解等),对其支持的业务动作和业务模型有清晰直观的元数据描述。建立业务流程元数据包括:业务动作列表和对应的触发点(操作),支持的业务模型列表2)实例核心体组件定义类publicinterfaceBizModelDiscountComponentextendsBizModelComponent{/***occupancydiscount*@paramcontext*/voidoccupy(FlowContextcontext);/***Refunddiscount*@paramcontext*/voidrefund(FlowContextcontext);}核心体组件元数据配置<triggerMapping>核心体组件模板实现适配器Adapter的解释,详见【模型适配】小节publicabstractclassAbstractBizModelDiscountComponentimplementsBizModelDiscountComponent{@ResourceprivateDiscountApiServicediscountApiService;@Overridepublicvoidoccupy(FlowContextcontext){//TODOAdapterConfigInfo根据context从当前合约中获取TbizModel=(T)context.getBizModel();getDiscountAdapter().processOnOccupyResult(bizModel,discountApiService.occupy(getDiscountAdapter().toOccupyInfo(bizModel,newAdapterConfigInfo())));}@Overridepublicvoidrefund(FlowContextcontext){//TODOAdapterConfigInfo根据context=(T)context.getBizModel();getDiscountAdapter()从当前合约中获取TbizModel。processOnRefundResult(bizModel,discountApiService.refund(getDiscountAdapter().toRefundInfo(bizModel,newAdapterConfigInfo())));}@Su警告(“未检查”)protectedBizModelToDiscountAdaptergetDiscountAdapter(){return(BizModelToDiscountAdapter)FlowInstanceFactory.instanceBizAdapter("DISCOUNT",(Class)TypeUtils.getRealClassOfParameterizedType(this));}}3)构建新的/修改硬编码:新的业务组件基本不能低编码。需要有足够的设计思维和大局观进行开发,权衡可复用程度和成本后实现第一个版本;随着业务的发展,逐步抽象出模板化业务组件的实现;在很多场景下,如果无法避免复杂的自定义逻辑,可以采用策略/责任链/工厂等多种设计模式来实现。这取决于开发者的建模能力,不需要低代码:现有的业务组件在应用到新业务模型的场景下,如果已经抽象出合约配置+适配器+基础设施的标准模板,只需要合约配置(通知/验证/存证上链等场景适用)使用低代码:在Flow基础上完成业务组件的编排/发现和触发,一次性编码完成;Flow配置完成,即完成业务组件的组装3Infrastructure注意:这里的infrastructure和DDD中的概念有很大区别,请不要混淆规范Infrastructure是一套对外的服务能力,复用度高、高内聚、低变化为单位(接口),对业务服务/业务组件开放的最小功能单元(方法)。基础设施可以是渠道能力的封装,比如对外商户渠道服务/跨境渠道服务等;也可以是对通用技术能力的封装,比如优惠服务/商品服务/客服等。基础设施和业务服务的区别在于,前者的核心功能通常由外部服务提供,核心职责在当前系统中是参数组装/场景识别/返回分析和不依赖于外部服务的异常处理基础设施的定义。入参为自定义标准POJO,返回值也封装在Result中,屏蔽对外服务异常和业务异常,业务返回也是标准POJO实例基础设施-信息通知publicinterfaceNotifyGateway{/***Notification(email/SMS/internalmessage)*@paramnotifyInfo*@return*/CommonResultnotify(NotifyInfonotifyInfo);}构建新的/修改的硬代码:基础设施访问通常是一次性的,值低码不好玩。使用硬编码:在业务服务/业务组件等调用方代码中,组装参数->调用->解析返回低代码:在业务组件中,基于下面描述的适配机制,可以实现:合约配置+模板化业务组件,现有基础设施的低代码复用4模型适配规范模型适配用于连接业务模型和基础设施/业务服务,实现模型->输入返回->模型在模板化业务组件中,适配器的调用链基础设施/业务服务得到巩固。每个业务模型的组件实例只需要实现相应的适配器即可完成业务定制。适配器通常与产品合同配置相结合。描述业务模型->基础设施/业务服务入参实例适配器-业务模型->网银签名publicabstractclassBizModelToDiscountAdapterimplementsBizModelAdapter{@OverridefinalpublicStringgetType(){return"DISCOUNT";}/***generate的映射关系buckle减法应用*@parambizModel*@return*/abstractpublicOccupyInfotoOccupyInfo(UbizModel,AdapterConfigInfoconfigInfo);/***处理减法结果*@parambizModel*@paramresult*/abstractpublicvoidprocessOnOccupyResult(UbizModel,CommonResultresult);//...}OrdermodelOrder,当需要使用折扣抵扣服务时,需要实现适配器BizModelToDiscountAdapter:@BizAdapterpublicclassOrderToDiscountAdapterextendsBizModelToDiscountAdapter{@OverridepublicListgetConfigDefs(){returnLists.newArrayList(ConfigEnum.DISCOUNT_TYPE,ConfigEnum.DISCOUNT_TERM);}@OverridepublicOccupyInfotoOccupyInfo(OrderbizModel,AdapterConfigInfoconfigInfo){//解析出客户选择的优惠类型returnnewOccupyInfo();}@OverridepublicvoidprocessOnOccupyResult(OrderbizModel,CommonResultresult){//TODO根据扣费成功的折扣重新计算订单金额}//...}新建/修改定义-硬编码:当业务组件和基础设施/被定义首次出现业务服务的调用关系时,通常不会再对实现做进一步的改动。低代码:可以用一套灵活的合约配置来描述映射关系,一次编码实现后只需要配置和维护;但是,这依赖于DSL级别的描述能力,同时也需要商业模型和基础设施/业务服务的设计者具有较高的抽象能力,成本较高。使用硬编码:业务开发时抽象出可模板化的业务组件,完成第一次访问;当出现新的基础架构/业务服务模型时,需要进行适应和调整。4.总结了这么多,为了避免过多的细节冲淡主题,最后结合几个问题做一个总结:1.业务设计规范体现在哪里?在架构层面,从产品契约->业务领域->基础设施,我们对应用模块进行了拆解,设计了不同层次的业务规则,约束了各个模块的职责;在技??术层面,通过多个基础组件,在一定程度上实现了平台与业务定制的隔离,限制了业务细节的无序分布。2商业设计只适合不标准。为什么它应该是强制性的?规范的目的不是标准本身,本文提出的标准可能并不适用于所有问题领域。我想传达的是,团队内部需要对业务设计有一定的共识和沉淀。基于每次迭代的需求和每个项目的产出,不断积累,不断重构,不断优化,有利于新人/个人成长与团队的融合。协作很有帮助。3如何快速支撑业务,研发效率提升体现在哪里?需要明确的是,对于全新的业务需求,并不会带来明显的性能提升,甚至为了满足设计规范,也会带来一定程度的额外成本。但是,当多人协作,工作交接,或者现有功能部分复用时,会减少很多不必要的沟通和维护成本。例如,当业务需求出现时,研发人员需要做出如下判断:业务模型:是否需要新的业务模型,或者现有模型是否需要调整业务服务:xxxxxxxxxxxx业务服务,xxxxxxxxxxxx现有服务业务流程:xxxxxxxxxxxxbusinessProcess,xxxxxxxxxxxxExistingProcessBusinessComponents:xxxxxxxxxxxxxxxBusinessComponents,xxxxxxxxxxxxExistingComponentsInfrastructure:xxxxxxxxxxxxInfrastructure,xxxxxxxxxxxxExistingFacilitiesProductContracts/ContractGroups:根据以上判断,评估产品契约和契约组装带来的有效性groups的改进有以下几点:业务领域各模块相互解耦,研发流程并行化,1+1可以等于2个投入人员;改造范围更容易定位,资源评估更准确,进度控制更清晰;针对频繁变动、成本过高的Module进行针对性重构,影响范围可控;上述许多规定都有低编码的潜力,可以进一步提高施工效率。