一文教会你如何写复杂业务代码

2020-10-28 21:26    来源:互联网    编辑:小优    浏览量:311

了解我的人都知道,我一直在致力于应用架构和代码复杂度的治理。我相信,同样的方可以复制到大部分复杂业务场景。一个复杂业务的处理过程业务背景简单的介绍下业务背景,零售通是给线下小店供货的 B2B 模式,我

一文教会你如何写复杂业务代码(图1)

了解我的人都知道,我一直在致力于应用架构和代码复杂度的治理。

我相信,同样的方可以复制到大部分复杂业务场景。

一个复杂业务的处理过程

业务背景

简单的介绍下业务背景,零售通是给线下小店供货的 B2B 模式,我们希望通过数字化重构传统供应链渠道,提升供应链效率,为新零售助力。阿里在中间是一个平台角色,提供的是 Bsbc 中的 service 的功能。

一文教会你如何写复杂业务代码(图2)

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

一文教会你如何写复杂业务代码(图3)

在上图中红框标识的是一个操作的“上架”动作,这是非常关键的业务操作。上架之后,商品就能在零售通上面对小店进行了。因为上架操作非常关键,所以也是商品域中最复杂的业务之一,涉及很多的数据校验和关联操作。

针对上架,一个简化的业务流程如下所示:

一文教会你如何写复杂业务代码(图4)

过程分解

像这么复杂的业务,我想应该没有人会写在一个 service 方法中吧。一个类解决不了,那就分治吧。

说实话,能想到分而治之的工程师,已经做的不错了,至少比没有分治思维要好很多。我也见过复杂程度相当的业务,连分解都没有,就是一堆方法和类的堆砌。

不过,这里存在一个问题:即很多同学过度的依赖工具或是辅助手段来实现分解。比如在我们的商品域中,类似的分解手段至少有 3 套以上,有自制的流程引擎,有依赖于数据库配置的流程处理:

一文教会你如何写复杂业务代码(图5)

本质上来讲,这些辅助手段做的都是一个 pipeline 的处理流程,没有其它。因此,我建议此处最好保持 KISS(Keep It Simple and Stupid)即最好是什么工具都不要用,次之是用一个极简的 Pipeline 模式,最差是使用像流程引擎这样的重方法。

回到商品上架的问题,这里问题核心是工具吗?是设计模式带来的代码灵活性吗?显然不是,问题的核心应该是如何分解问题和抽象问题,知道金字塔原理的应该知道,此处,我们可以使用结构化分解将问题解构成一个有层级的金字塔结构:

一文教会你如何写复杂业务代码(图6)

按照这种分解写的代码,就像一本书,目录和内容清晰明了。

以商品上架为例,程序的入口是一个上架命令(OnSaleCommand) 它由三个阶段(Phase)组成。

@Command

public 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 组成的:

@Phase

public 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)上。

一文教会你如何写复杂业务代码(图7)

过程分解后的两个问题

1、领域知识被割裂肢解

什么叫被肢解?因为我们到目前为止做的都是过程化拆解,导致没有一个聚合领域知识的地方。每个 Use Case 的代码只关心自己的处理流程,知识没有沉淀。

相同的业务逻辑会在多个 Use Case 中被重复实现,导致代码重复度高,即使有复用,最多也就是抽取一个 util,代码对业务语义的表达能力很弱,从而影响代码的可读性和可理解性。

2、代码的业务表达能力缺失

试想下,在过程式的代码中,所做的事情无外乎就是取数据 -- 做计算 -- 存数据,在这种情况下,要如何通过代码显性化的表达我们的业务呢? 说实话,很难做到,因为我们缺失了模型,以及模型之间的关系。脱离模型的业务表达,是缺少韵律和灵魂的。 举个例子,在上架过程中,有一个校验是检查库存的,其中对于组合品(CombineBackOffer)其库存的处理会和普通品不一样。原来的代码是这么写的:

boolean isCombineProduct = supplierItem.getSign.isCombProductQuote。

// supplier.usc warehouse neednt check

if WarehouseTypeEnum.isAliWarehousesupplierItem.getWarehouseType

// quote warehosue check

if (CollectionUtil.isEmpty(supplierItem.getWarehouseIdList) && !isCombineProduct)

throw ExceptionFactory.makeFaultServiceExceptionCode.SYSTEM_ERROR, “亲,不能发布Offer,请联系仓配人员,建立品仓关系!”。

// inventory amount check

Long 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.makeFaultServiceExceptionCode.SYSTEM_ERROR, “亲,实仓库存必须大于0才能发布,请确认已补货.rid:” + supplierItem.getId + 。

然而,如果我们在中引入领域模型之后,其代码会简化为如下:

if(backOffer.isCloudWarehouse)

return。

if (backOffer.isNonInWarehouse)

throw new BizException“亲,不能发布Offer,请联系仓配人员,建立品仓关系!”。

if (backOffer.getStockAmount < 1)

throw new BizException“亲,实仓库存必须大于0才能发布,请确认已补货.rid:” + backOffer.getSupplierItem.getCspuCode + 。

有没有发现,使用模型的表达要清晰易懂很多,而且也不需要做关于组合品的判断了,因为我们在中引入了更加贴近现实的对象模型(CombineBackOffer 继承 BackOffer)通过对象的多态可以消除我们代码中的大部分的 if-else。

一文教会你如何写复杂业务代码(图8)

过程分解+对象模型

通过上面的案例,我们可以看到有过程分解要好于没有分解,过程分解+对象模型要好于仅仅是过程分解。对于商品上架这个 case,如果采用过程分解+对象模型的方式,最终我们会得到一个如下的结构:

一文教会你如何写复杂业务代码(图9)

写复杂业务的方

通过上面案例的讲解,我想说,我已经交代了复杂业务代码要怎么写:即自上而下的结构化分解+自下而上的面向对象分析。

上下结合

所谓上下结合,是指我们要结合自上而下的过程分解和自下而上的对象建模,螺旋式的构建我们的应用。这是一个动态的过程,两个步骤可以交替进行、也可以同时进行。

这两个步骤是相辅相成的,上面的分析可以帮助我们更好的理清模型之间的关系,而下面的模型表达可以提升我们代码的复用度和业务语义表达能力。

一文教会你如何写复杂业务代码(图10)

使用这种上下结合的方式,我们就有可能在面对任何复杂的业务场景,都能写出干净整洁、易维护的代码。

能力下沉

一般来说实践 DDD 有两个过程:

1. 套概念阶段

了解了一些 DDD 的概念,在代码中“使用”Aggregation Root,Bounded Context,Repository 等等这些概念。更进一步,也会使用一定的分层策略。然而这种做法一般对复杂度的治理并没有多大作用。

2. 融会贯通阶段

术语已经不再重要,理解 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 层,从而增加代码的复用性。

一文教会你如何写复杂业务代码(图11)

指导下沉有两个关键指标:代码的复用性和内聚性。

复用性是告诉我们 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)的形式散落在代码的各个角落。这种代码的可理解性是可想而知的,至少我在第一眼看到这个代码的时候,是完全不知道什么意思。

业务技术要怎么做

写到这里,我想顺便回答一下很多业务技术同学的困惑,也是我之前的困惑:即业务技术到底是在做业务,还是做技术?业务技术的技术性体现在哪里?

通过上面的案例,我们可以看到业务所面临的复杂性并不亚于底层技术,要想写好业务代码也不是一件容易的事情。业务技术和底层技术人员唯一的区别是他们所面临的问题域不一样。

但是,不管是业务技术还是底层技术人员,有一些思维和能力都是共通的。比如,分解问题的能力,抽象思维,结构化思维等等。

一文教会你如何写复杂业务代码(图12)

用我的话说就是:做不好业务的,也做不好技术底层,反之亦然。业务一点都不简单,只是我们很多人把它做简单了

因此,如果从变化的角度来看,业务技术的难度一点不逊色于底层技术,其面临的甚至更大。因此,我想对广大的从事业务技术的同学说:沉下心来,夯实自己的基础技术能力、OO 能力、建模能力... 不断提升抽象思维、结构化思维、思辨思维... 持续学习精进,写好代码。我们可以在业务技术岗做的很”技术“

本文相关词条概念解析:

代码

代码就是程序员用开发工具所支持的语言写出来的源文件,是一组由字符、符号或信号码元以离散形式表示信息的明确的规则体系。代码设计的原则包括惟一确定性、标准化和通用性、可扩充性与稳定性、便于识别与记忆、力求短小与格式统一以及容易修改等。源代码是代码的分支,某种意义上来说,源代码相当于代码。在现代程序语言中,源代码可以是以书籍或者磁带的形式出现,但最为常用的格式是文本文件,这种典型格式的目的是为了编译出计算机程序。计算机源代码的最终目的是将人类可读的文本翻译成为计算机可以执行的二进制指令,这种过程叫做编译,它由通过编译器完成。

相关资讯

相关评论

推荐阅读

首回合对阵珀斯光荣的比赛申花2:1险胜,这场比赛崔康熙将会继续祭出双塔,球队的目标也是三分

首回合对阵珀斯光荣的比赛申花2:1险胜,这场比赛崔康熙将会继续祭出双塔,球队的目标也是三分
本赛季亚冠中超球队表现出奇的好,北京国安一波四连胜已经是提前小组出线了,上海上港下一场比赛只要拿到一分也能够出线。广州恒大则是获得了本届亚冠联赛的首胜,中超球队成为了区冠军的热门。目前上海申花高开低走
12-04

不下课,完成俱乐部全华班的目标,给卡纳瓦罗一些时间

不下课,完成俱乐部全华班的目标,给卡纳瓦罗一些时间
随着广州恒大1-1战平水原三星,球队出线的主动权拱手让人,国内联赛又丢了冠军,卡纳瓦罗被推上了风口浪尖。西班牙著名媒体《马卡报》甚至发文称,卡纳瓦罗距离周五神户胜利船和水原三星的比赛,只有48小时留任
12-04

稳坐第一,国安VS首尔主力11人巨变,3名外援轮休+6名替补将会强势入替,6替补强势入替

稳坐第一,国安VS首尔主力11人巨变,3名外援轮休+6名替补将会强势入替,6替补强势入替
北京国安是上个赛季中超参加亚冠球队唯一一个小组没有出线的球队,本赛季四连胜成为了中超球队当中第一个出线的。在联赛当中表现一般的国安在亚冠当中大发神威,目前已经取得了四连胜。张玉宁、阿兰、比埃拉和奥古斯
12-04

幽默笑话,老婆,我早就对你说过,你一文不值

幽默笑话,老婆,我早就对你说过,你一文不值
幽默笑话我在一家美容院等我朋友下班,发现他们美容院墙上整了个大液晶屏,除了一般的宣传海报外,还滚动播放主治医生自己拍摄的世界各国风景,顿时觉得这个老板还是很良心的,至少让患者看见“你们花的钱都去哪儿了
12-01

我认为是低代码,Low-Code中的Low

我认为是低代码,Low-Code中的Low
简介:什么是低代码?我们为什么需要低代码?低代码会让程序员失业吗?本文总结了低代码领域的基本概念、核心价值与行业现状,带你全面了解低代码。一 前言如果选择用一个关键词来代表即将过去的2020年,我相信
11-17

华为与苏迪,信大捷安的联合方案发布

华为与苏迪,信大捷安的联合方案发布
12月4日,华为携手苏州苏迪智能有限公司(以下简称“苏迪”、郑州信大捷安信息技术股份有限公司(以下简称“信大捷安”在华为苏州研究所正式发布车路协同领域的场景化联合解决方案,涵括华为与苏迪智能公交车联网
12-04

会问程序员的工作到底是干什么的

会问程序员的工作到底是干什么的
相信大家只要是从事计算机相关工作的,都会有身边人(主要是妹子)会问程序员的工作到底是干什么的?可能大部分同学脑补以为程序员一天到晚的工作就是写代码,写代码,写代码!读代码通常程序员在进入公司以后,不会
11-28

重回巅峰,在穆里尼奥的执教生涯中,更赢了过程

重回巅峰,在穆里尼奥的执教生涯中,更赢了过程
自去年11月份接替波切蒂诺执教热刺以来,穆里尼奥已经入主白鹿巷球场一年时间。在这一年时间里,无论是在进攻端还是在防守端,穆里尼奥都让人看到了热刺的明显变化。尤其是本赛季,在首轮输球的情况下,穆里尼奥的
12-04

傅向升表示,才称得上是清洁能源

傅向升表示,才称得上是清洁能源
“只有光伏、风电等可再生能源电解水所获得的氢才属于‘绿氢’才称得上是清洁能源。”在12月4日于杭州举办的氢能产业链专题论坛上,中国石油和化学工业联合会副会长傅向升表示,从整个产业链来看,氢能要成为真正
12-04

海鱼这么做没有腥味,鲜嫩入味,营养好吃到爆炸

海鱼这么做没有腥味,鲜嫩入味,营养好吃到爆炸
今天早上买了一些新鲜的海杂鱼,加点自己冻的冻豆腐,焖上一锅真是鲜美无比又营养丰富,下饭下酒都是棒棒哒…准备的食材海杂鱼600克、冻豆腐1块、青红辣椒各半个、姜2片、小葱1根、大蒜2瓣、香菜少许、花椒1
12-04

热点资讯

精彩推荐

热门美图

网站地图

wingming.com.cn 永明资讯站