专栏名称: 架构文摘
每天一篇架构领域重磅好文,涉及一线互联网公司的互联网应用架构、大数据、机器学习等各个热门领域。
目录
相关文章推荐
架构师之路  ·  别TM浪费算力了,这样才能最大限度发挥dee ... ·  3 天前  
高可用架构  ·  漫谈DeepSeek及其背后的核心技术 ·  2 天前  
美团技术团队  ·  CVPR 2025 NTIRE赛事 | ... ·  2 天前  
51好读  ›  专栏  ›  架构文摘

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

架构文摘  · 公众号  · 架构  · 2019-09-12 09:00

正文

这两天在看零售通商品域的代码。面对零售通如此复杂的业务场景,如何在架构和代码层面进行应对,是一个新课题。针对该命题,我进行了比较细致的思考和研究。结合实际的业务场景,我沉淀了一套“如何写复杂业务代码”的方法论,在此分享给大家。

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

一个复杂业务的处理过程

业务背景

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

在商品域,运营会操作一个“上架”动作,上架之后,商品就能在零售通上面对小店进行销售了。 是零售通业务非常关键的业务操作之一,因此涉及很多的数据校验和关联操作

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

过程分解

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

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

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

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

除非你的应用有极强的流程可视化和编排的诉求,否则我非常不推荐使用流程引擎等工具。第一,它会引入额外的复杂度,特别是那些需要持久化状态的流程引擎;第二,它会割裂代码,导致阅读代码的不顺畅。 大胆断言一下,全天下估计80%对流程引擎的使用都是得不偿失的

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

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

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

  1. @Command

  2. public class OnSaleNormalItemCmdExe {


  3. @Resource

  4. private OnSaleContextInitPhase onSaleContextInitPhase;

  5. @Resource

  6. private OnSaleDataCheckPhase onSaleDataCheckPhase;

  7. @Resource

  8. private OnSaleProcessPhase onSaleProcessPhase;


  9. @Override

  10. public Response execute(OnSaleNormalItemCmd cmd) {


  11. OnSaleContext onSaleContext = init(cmd);


  12. checkData(onSaleContext);


  13. process(onSaleContext);


  14. return Response.buildSuccess();

  15. }


  16. private OnSaleContext init(OnSaleNormalItemCmd cmd) {

  17. return onSaleContextInitPhase.init(cmd);

  18. }


  19. private void checkData(OnSaleContext onSaleContext) {

  20. onSaleDataCheckPhase.check(onSaleContext);

  21. }


  22. private void process(OnSaleContext onSaleContext) {

  23. onSaleProcessPhase.process(onSaleContext);

  24. }

  25. }

每个Phase又可以拆解成多个步骤(Step),以 OnSaleProcessPhase 为例,它是由一系列Step组成的:

  1. @Phase

  2. public class OnSaleProcessPhase {


  3. @Resource

  4. private PublishOfferStep publishOfferStep;

  5. @Resource

  6. private BackOfferBindStep backOfferBindStep;

  7. //省略其它step


  8. public void process(OnSaleContext onSaleContext){

  9. SupplierItem supplierItem = onSaleContext.getSupplierItem();


  10. // 生成OfferGroupNo

  11. generateOfferGroupNo(supplierItem);


  12. // 发布商品

  13. publishOffer(supplierItem);


  14. // 前后端库存绑定 backoffer域

  15. bindBackOfferStock(supplierItem);


  16. // 同步库存路由 backoffer域

  17. syncStockRoute(supplierItem);


  18. // 设置虚拟商品拓展字段

  19. setVirtualProductExtension(supplierItem);


  20. // 发货保障打标 offer域

  21. markSendProtection(supplierItem);


  22. // 记录变更内容ChangeDetail

  23. recordChangeDetail(supplierItem);


  24. // 同步供货价到BackOffer

  25. syncSupplyPriceToBackOffer(supplierItem);


  26. // 如果是组合商品打标,写扩展信息

  27. setCombineProductExtension(supplierItem);


  28. // 去售罄标

  29. removeSellOutTag(offerId);


  30. // 发送领域事件

  31. fireDomainEvent(supplierItem);


  32. // 关闭关联的待办事项

  33. closeIssues(supplierItem);

  34. }

  35. }

看到了吗,这就是商品上架这个复杂业务的业务流程。需要流程引擎吗?不需要,需要设计模式支撑吗?也不需要。对于这种业务流程的表达,简单朴素的组合方法模式(Composed Method)是再合适不过的了。

因此,在做过程分解的时候,我建议工程师不要把太多精力放在工具上,放在设计模式带来的灵活性上。而是应该多花时间在对问题分析,结构化分解,最后通过合理的抽象,形成合适的阶段(Phase)和步骤(Step)上。z

过程分解后的两个问题

的确,使用过程分解之后的代码,已经比以前的代码更清晰、更容易维护了。不过,还有两个问题值得我们去关注一下:

1、领域知识被割裂肢解

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

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

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

试想下,在过程式的代码中,所做的事情无外乎就是取数据--做计算--存数据,在这种情况下,要如何通过代码显性化的表达我们的业务呢?说实话,很难做到,因为我们缺失了模型,以及模型之间的关系。脱离模型的业务表达,是缺少韵律和灵魂的。

举个例子,在上架过程中,有一个校验是检查库存的,其中对于组合品(CombineBackOffer)其库存的处理会和普通品不一样。原来的代码是这么写的:

  1. boolean isCombineProduct = supplierItem.getSign().isCombProductQuote();


  2. // supplier.usc warehouse needn't check

  3. if (WarehouseTypeEnum.isAliWarehouse(supplierItem.getWarehouseType())) {

  4. // quote warehosue check

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

  6. throw ExceptionFactory.makeFault(ServiceExceptionCode.SYSTEM_ERROR, "亲,不能发布Offer,请联系仓配运营人员,建立品仓关系!");

  7. }

  8. // inventory amount check

  9. Long sellableAmount = 0L;

  10. if (!isCombineProduct) {

  11. sellableAmount = normalBiz.acquireSellableAmount(supplierItem.getBackOfferId(), supplierItem.getWarehouseIdList());

  12. } else {

  13. //组套商品

  14. OfferModel backOffer = backOfferQueryService.getBackOffer(supplierItem.getBackOfferId());

  15. if (backOffer != null) {

  16. sellableAmount = backOffer.getOffer().getTradeModel().getTradeCondition().getAmountOnSale();

  17. }

  18. }

  19. if (sellableAmount < 1) {

  20. throw ExceptionFactory.makeFault(ServiceExceptionCode.SYSTEM_ERROR, "亲,实仓库存必须大于0才能发布,请确认已补货.\r[id:" + supplierItem.getId() + "]");

  21. }

  22. }

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

  1. if(backOffer.isCloudWarehouse()){

  2. return;

  3. }


  4. if (backOffer.isNonInWarehouse()){

  5. throw new BizException("亲,不能发布Offer,请联系仓配运营人员,建立品仓关系!");

  6. }


  7. if (backOffer.getStockAmount() < 1){

  8. throw new BizException("亲,实仓库存必须大于0才能发布,请确认已补货.\r[id:" + backOffer.getSupplierItem().getCspuCode() + "]");

  9. }

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

过程分解+对象模型

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

写复杂业务的方法论

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

接下来,让我们把上面的案例进行进一步的提炼,形成一个可落地的方法论,从而可以泛化到更多的复杂业务场景。







请到「今天看啥」查看全文