阿里高级技能专家方法论:怎样写复杂业务代码?

源码 2024-9-17 14:03:31 16 0 来自 中国
阿里妹导读:张建飞是阿里巴巴高级技能专家,不停在致力于应用架构和代码复杂度的管理。最近,他在看零售通商品域的代码。面对零售通云云复杂的业务场景,怎样在架构和代码层面举行应对,是一个新课题。结合现实的业务场景,Frank 沉淀了一套“怎样写复杂业务代码”的方法论,在此分享给各人,信托同样的方法论可以复制到大部门复杂业务场景。
一个复杂业务的处理处罚过程
业务配景
简朴的先容下业务配景,零售通是给线下小店供货的B2B模式,我们盼望通过数字化重构传统供应链渠道,提升供应链服从,为新零售助力。阿里在中心是一个平台角色,提供的是Bsbc中的service的功能。


商品力是零售通的核心所在,一个商品在零售通的生命周期如下图所示:

在上图中红框标识的是一个运营操纵的“上架”动作,这黑白常关键的业务操纵。上架之后,商品就能在零售通上面对小店举行贩卖了。由于上架操纵非常关键,以是也是商品域中最复杂的业务之一,涉及许多的数据校验和关联操纵。针对上架,一个简化的业务流程如下所示:

过程分解
像这么复杂的业务,我想应该没有人会写在一个service方法中吧。一个类办理不了,那就分治吧。说实话,能想到分而治之的工程师,已经做的不错了,至少比没有分治头脑要好许多。我也见过复杂水平相称的业务,连分解都没有,就是一堆方法和类的堆砌。不外,这里存在一个标题:即许多同砚过分的依赖工具或是辅助本领来实现分解。比如在我们的商品域中,雷同的分解本领至少有3套以上,有自制的流程引擎,有依赖于数据库设置的流程处理处罚:

4.png
本质上来讲,这些辅助本领做的都是一个pipeline的处理处罚流程,没有别的。因此,我发起此处最好保持KISS(Keep It Simple and Stupid),即最好是什么工具都不要用,次之是用一个极简的Pipeline模式,最差是使用像流程引擎如许的重方法。除非你的应用有极强的流程可视化和编排的诉求,否则我非常不保举使用流程引擎等工具。第一,它会引入额外的复杂度,特别是那些必要长期化状态的流程引擎;第二,它会割裂代码,导致阅读代码的不顺畅。大胆断言一下,全天下估计80%对流程引擎的使用都是得不偿失的。回到商品上架的标题,这里标题核心是工具吗?是筹划模式带来的代码灵活性吗?显然不是,标题的核心应该是怎样分解标题和抽象标题,知道金字塔原理的应该知道,此处,我们可以使用布局化分解将标题解构成一个有层级的金字塔布局


按照这种分解写的代码,就像一本书,目次和内容清晰明白。以商品上架为例,步调的入口是一个上架下令(OnSaleCommand), 它由三个阶段(Phase)构成。
@Commandpublic class OnSaleNormalItemCmdExe {    @Resource    private OnSaleContextInitPhase onSaleContextInitPhase;    @Resource    private OnSaleDataCheckPhase onSaleDataCheckPhase;    @Resource    private OnSaleProcessPhase onSaleProcessPhase;    @Override    public Response execute(OnSaleNormalItemCmd cmd) {        OnSaleContext onSaleContext = init(cmd);        checkData(onSaleContext);        process(onSaleContext);        return Response.buildSuccess();    }    private OnSaleContext init(OnSaleNormalItemCmd cmd) {        return onSaleContextInitPhase.init(cmd);    }    private void checkData(OnSaleContext onSaleContext) {        onSaleDataCheckPhase.check(onSaleContext);    }    private void process(OnSaleContext onSaleContext) {        onSaleProcessPhase.process(onSaleContext);    }}每个Phase又可以拆解成多个步骤(Step),以OnSaleProcessPhase为例,它是由一系列Step构成的:
@Phasepublic class OnSaleProcessPhase {    @Resource    private PublishOfferStep publishOfferStep;    @Resource    private BackOfferBindStep backOfferBindStep;    //省略别的step    public void process(OnSaleContext onSaleContext){        SupplierItem supplierItem = onSaleContext.getSupplierItem();        // 天生OfferGroupNo        generateOfferGroupNo(supplierItem);       // 发布商品        publishOffer(supplierItem);        // 前后端库存绑定 backoffer域        bindBackOfferStock(supplierItem);        // 同步库存路由 backoffer域        syncStockRoute(supplierItem);        // 设置假造商品拓展字段        setVirtualProductExtension(supplierItem);        // 发货保障打标 offer域        markSendProtection(supplierItem);        // 记载变更内容ChangeDetail        recordChangeDetail(supplierItem);        // 同步供货价到BackOffer        syncSupplyPriceToBackOffer(supplierItem);        // 假如是组合商品打标,写扩展信息        setCombineProductExtension(supplierItem);        // 去售罄标        removeSellOutTag(offerId);        // 发送范畴变乱        fireDomainEvent(supplierItem);        // 关闭关联的待服务项        closeIssues(supplierItem);    }}看到了吗,这就是商品上架这个复杂业务的业务流程。必要流程引擎吗?不必要,必要筹划模式支持吗?也不必要。对于这种业务流程的表达,简朴淳厚的组合方法模式(Composed Method)是再符合不外的了。因此,在做过程分解的时间,我发起工程师不要把太多精神放在工具上,放在筹划模式带来的灵活性上。而是应该多花时间在对标题分析,布局化分解,末了通过公道的抽象,形成符合的阶段(Phase)和步骤(Step)上。

6.png 过程分解后的两个标题
简直,使用过程分解之后的代码,已经比从前的代码更清晰、更轻易维护了。不外,尚有两个标题值得我们去关注一下:
★ 范畴知识被割裂肢解
什么叫被肢解?由于我们到现在为止做的都是过程化拆解,导致没有一个聚合范畴知识的地方。每个Use Case的代码只关心本身的处理处罚流程,知识没有沉淀。相同的业务逻辑会在多个Use Case中被重复实现,导致代码重复度高,纵然有复用,最多也就是抽取一个util,代码对业务语义的表达本领很弱,从而影响代码的可读性和可明白性。
★ 代码的业务表达本领缺失
试想下,在过程式的代码中,所做的事变无外乎就是取数据--做盘算--存数据,在这种环境下,要怎样通过代码显性化的表达我们的业务呢?说实话,很难做到,由于我们缺失了模型,以及模型之间的关系。离开模型的业务表达,是缺少韵律和灵魂的。举个例子,在上架过程中,有一个校验是查抄库存的,此中对于组合品(CombineBackOffer)其库存的处理处罚会宁静凡品不一样。原来的代码是这么写的:
boolean isCombineProduct = supplierItem.getSign().isCombProductQuote();// supplier.usc warehouse needn't checkif (WarehouseTypeEnum.isAliWarehouse(supplierItem.getWarehouseType())) {// quote warehosue checkif (CollectionUtil.isEmpty(supplierItem.getWarehouseIdList()) && !isCombineProduct) {    throw ExceptionFactory.makeFault(ServiceExceptionCode.SYSTEM_ERROR, "亲,不能发布Offer,请接洽仓配运营职员,创建品仓关系!");}// inventory amount checkLong sellableAmount = 0L;if (!isCombineProduct) {    sellableAmount = normalBiz.acquireSellableAmount(supplierItem.getBackOfferId(), supplierItem.getWarehouseIdList());} else {    //组套商品    OfferModel backOffer = backOfferQueryService.getBackOffer(supplierItem.getBackOfferId());    if (backOffer != null) {        sellableAmount = backOffer.getOffer().getTradeModel().getTradeCondition().getAmountOnSale();    }}if (sellableAmount < 1) {    throw ExceptionFactory.makeFault(ServiceExceptionCode.SYSTEM_ERROR, "亲,实堆栈存必须大于0才气发布,请确认已补货.\r[id:" + supplierItem.getId() + "]");}}然而,假如我们在体系中引入范畴模型之后,其代码会简化为如下:
if(backOffer.isCloudWarehouse()){    return;}if (backOffer.isNonInWarehouse()){    throw new BizException("亲,不能发布Offer,请接洽仓配运营职员,创建品仓关系!");}if (backOffer.getStockAmount() < 1){    throw new BizException("亲,实堆栈存必须大于0才气发布,请确认已补货.\r[id:" + backOffer.getSupplierItem().getCspuCode() + "]");}有没有发现,使用模型的表达要清晰易懂许多,而且也不必要做关于组合品的判断了,由于我们在体系中引入了更加贴近现实的对象模型(CombineBackOffer继承BackOffer),通过对象的多态可以消除我们代码中的大部门的if-else。

过程分解+对象模型
通过上面的案例,我们可以看到有过程分解要好于没有分解,过程分解+对象模型要好于仅仅是过程分解。对于商品上架这个case,假如接纳过程分解+对象模型的方式,终极我们会得到一个如下的体系布局:


写复杂业务的方法论
通过上面案例的解说,我想说,我已经交代了复杂业务代码要怎么写:即自上而下的布局化分解+自下而上的面向对象分析。接下来,让我们把上面的案例举行进一步的提炼,形成一个可落地的方法论,从而可以泛化到更多的复杂业务场景。上下结合所谓上下结合,是指我们要结合自上而下的过程分解和自下而上的对象建模,螺旋式的构建我们的应用体系。这是一个动态的过程,两个步骤可以瓜代举行、也可以同时举行。这两个步骤是相辅相成的,上面的分析可以资助我们更好的理清模型之间的关系,而下面的模型表达可以提升我们代码的复用度和业务语义表达本领。其过程如下图所示:

9.png
使用这种上下结合的方式,我们就有大概在面对任何复杂的业务场景,都能写出干净整齐、易维护的代码。
本领下沉一样寻常来说实践DDD有两个过程:
★ 套概念阶段:
相识了一些DDD的概念,然后在代码中“使用”Aggregation Root,Bounded Context,Repository等等这些概念。更进一步,也会使用肯定的分层计谋。然而这种做法一样寻常对复杂度的管理并没有多大作用。
★ 领悟贯通阶段:
术语已经不再紧张,明白DDD的本质是同一语言、边界分别和面向对象分析的方法。大要上而言,我大概是在1.7的阶段,由于有一个标题不停在困扰我,就是哪些本领应该放在Domain层,是不是按照传统的做法,将全部的业务都收拢到Domain上,如许做公道吗?说实话,这个标题我不停没有想清晰。由于在现实业务中,许多的功能都是用例特有的(Use case specific)的,假如“盲目”的使用Domain收拢业务并不见得能带来多大的益处。相反,这种收拢会导致Domain层的膨胀过厚,不敷纯粹,反而会影响复用性和表达本领。鉴于此,我最近的思索是我们应该接纳本领下沉的计谋。所谓的本领下沉,是指我们不强求一次就能筹划出Domain的本领,也不必要欺凌要求把全部的业务功能都放到Domain层,而是接纳实用主义的态度,即只对那些必要在多个场景中必要被复用的本领举行抽象下沉,而不必要复用的,就临时放在App层的Use Case里就好了。注:Use Case是《架构整齐之道》内里的术语,简朴明白就是相应一个Request的处理处罚过程。通过实践,我发现这种循规蹈矩的本领下沉计谋,应该是一种更符合现实、更敏捷的方法。****由于我们承认模型不是一次性筹划出来的,而是迭代演化出来的。下沉的过程如下图所示,假设两个use case中,我们发现uc1的step3和uc2的step1有雷同的功能,我们就可以思量让其下沉到Domain层,从而增长代码的复用性。

10.png
引导下沉有两个关键指标:

  • 复用性
  • 内聚性
复用性是告诉我们When(什么时间该下沉了),即有重复代码的时间。内聚性是告诉我们How(要下沉到那里),功能有没有内聚到适当的实体上,有没有放到符合的条理上(由于Domain层的本领也是有两个条理的,一个是Domain Service这是相对比较粗的粒度,另一个是Domain的Model这个是最细粒度的复用)。比如,在我们的商品域,常常必要判断一个商品是不是最小单元,是不是中包商品。像这种本领就非常有须要直接挂载在Model上。
public class CSPU {    private String code;    private String baseCode;    //省略别的属性    /**     * 单品是否为最小单元。     *     */    public boolean isMinimumUnit(){        return StringUtils.equals(code, baseCode);    }    /**     * 针对中包的特别处理处罚     *     */    public boolean isMidPackage(){        return StringUtils.equals(code, midPackageCode);    }}之前,由于老体系中没有范畴模型,没有CSPU这个实体。你会发现像判断单品是否为最小单元的逻辑是以StringUtils.equals(code, baseCode)的情势散落在代码的各个角落。这种代码的可明白性是可想而知的,至少我在第一眼看到这个代码的时间,是完全不知道什么意思。业务技能要怎么做写到这里,我想趁便答复一下许多业务技能同砚的狐疑,也是我之前的狐疑:即业务技能到底是在做业务,还是做技能?业务技能的技能性体如今那里?通过上面的案例,我们可以看到业务所面对的复杂性并不亚于底层技能,要想写好业务代码也不是一件轻易的事变。业务技能和底层技能职员唯一的区别是他们所面对的标题域不一样。业务技能面对的标题域变化更多、面对的人更加繁芜。而底层技能面对的标题域更加稳固、但对技能的要求更加深。比如,假如你必要去开发Pandora,你就要对Classloader有更加深入的相识才行。但是,不管是业务技能还是底层技能职员,有一些头脑和本领都是共通的。比如,分解标题的本领,抽象头脑,布局化头脑等等。

11.png
用我的话说就是:
“做欠好业务开发的,也做欠好技能底层开发,反之亦然。
业务开发一点都不简朴,只是我们许多人把它做“简朴”了。
因此,假如从变化的角度来看,业务技能的难度一点不逊色于底层技能,其面对的寻衅乃至更大。因此,我想对广大的从事业务技能开发的同砚说:沉下心来,夯实本身的根本技能本领、OO本领、建模本领... 不停提升抽象头脑、布局化头脑、思辨头脑... 一连学习精进,写好代码。我们可以在业务技能岗做的很”技能“!。
跋文
这篇文章是我最近思索的一些总结,大部门头脑是继承自我原来写的COLA架构,该架构已经开源,现在在团体内外都有比较广泛的使用。这一篇紧张是在COLA的根本上,针对复杂业务场景,做了进一步的架构落地。个人感觉可以作为COLA的最佳实践来使用。别的,本文讨论的标题之大和篇幅之短是不成正比的。原因是我假定你已经相识了一些DDD和应用架构的根本知识。假如以为在明白上有困难,我发起可以先看下《范畴驱动筹划》和《架构整齐之道》这两本书。假如没有那么多时间,也可以快速欣赏下我之前的两篇文章应用架构之道 和 范畴建模去知晓一下我之前的头脑脉络。
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-19 00:24, Processed in 0.160866 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表