自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

阿里高級(jí)技術(shù)專家方法論:如何寫復(fù)雜業(yè)務(wù)代碼?

開發(fā) 項(xiàng)目管理
簡(jiǎn)單的介紹下業(yè)務(wù)背景,零售通是給線下小店供貨的B2B模式,我們希望通過數(shù)字化重構(gòu)傳統(tǒng)供應(yīng)鏈渠道,提升供應(yīng)鏈效率,為新零售助力。阿里在中間是一個(gè)平臺(tái)角色,提供的是Bsbc中的service的功能。

[[273698]]

張建飛是阿里巴巴高級(jí)技術(shù)專家,一直在致力于應(yīng)用架構(gòu)和代碼復(fù)雜度的治理。最近,他在看零售通商品域的代碼。面對(duì)零售通如此復(fù)雜的業(yè)務(wù)場(chǎng)景,如何在架構(gòu)和代碼層面進(jìn)行應(yīng)對(duì),是一個(gè)新課題。結(jié)合實(shí)際的業(yè)務(wù)場(chǎng)景,F(xiàn)rank 沉淀了一套“如何寫復(fù)雜業(yè)務(wù)代碼”的方法論,在此分享給大家,相信同樣的方法論可以復(fù)制到大部分復(fù)雜業(yè)務(wù)場(chǎng)景。

一個(gè)復(fù)雜業(yè)務(wù)的處理過程

業(yè)務(wù)背景

簡(jiǎn)單的介紹下業(yè)務(wù)背景,零售通是給線下小店供貨的B2B模式,我們希望通過數(shù)字化重構(gòu)傳統(tǒng)供應(yīng)鏈渠道,提升供應(yīng)鏈效率,為新零售助力。阿里在中間是一個(gè)平臺(tái)角色,提供的是Bsbc中的service的功能。

商品力是零售通的核心所在,一個(gè)商品在零售通的生命周期如下圖所示:

在上圖中紅框標(biāo)識(shí)的是一個(gè)運(yùn)營(yíng)操作的“上架”動(dòng)作,這是非常關(guān)鍵的業(yè)務(wù)操作。上架之后,商品就能在零售通上面對(duì)小店進(jìn)行銷售了。因?yàn)樯霞懿僮鞣浅jP(guān)鍵,所以也是商品域中最復(fù)雜的業(yè)務(wù)之一,涉及很多的數(shù)據(jù)校驗(yàn)和關(guān)聯(lián)操作。針對(duì)上架,一個(gè)簡(jiǎn)化的業(yè)務(wù)流程如下所示:

過程分解

像這么復(fù)雜的業(yè)務(wù),我想應(yīng)該沒有人會(huì)寫在一個(gè)service方法中吧。一個(gè)類解決不了,那就分治吧。

說實(shí)話,能想到分而治之的工程師,已經(jīng)做的不錯(cuò)了,至少比沒有分治思維要好很多。我也見過復(fù)雜程度相當(dāng)?shù)臉I(yè)務(wù),連分解都沒有,就是一堆方法和類的堆砌。

不過,這里存在一個(gè)問題:即很多同學(xué)過度的依賴工具或是輔助手段來(lái)實(shí)現(xiàn)分解。比如在我們的商品域中,類似的分解手段至少有3套以上,有自制的流程引擎,有依賴于數(shù)據(jù)庫(kù)配置的流程處理:

本質(zhì)上來(lái)講,這些輔助手段做的都是一個(gè)pipeline的處理流程,沒有其它。因此,我建議此處最好保持KISS(Keep It Simple and Stupid),即最好是什么工具都不要用,次之是用一個(gè)極簡(jiǎn)的Pipeline模式,最差是使用像流程引擎這樣的重方法。

除非你的應(yīng)用有極強(qiáng)的流程可視化和編排的訴求,否則我非常不推薦使用流程引擎等工具。第一,它會(huì)引入額外的復(fù)雜度,特別是那些需要持久化狀態(tài)的流程引擎;第二,它會(huì)割裂代碼,導(dǎo)致閱讀代碼的不順暢。大膽斷言一下,全天下估計(jì)80%對(duì)流程引擎的使用都是得不償失的。

回到商品上架的問題,這里問題核心是工具嗎?是設(shè)計(jì)模式帶來(lái)的代碼靈活性嗎?顯然不是,問題的核心應(yīng)該是如何分解問題和抽象問題,知道金字塔原理的應(yīng)該知道,此處,我們可以使用結(jié)構(gòu)化分解將問題解構(gòu)成一個(gè)有層級(jí)的金字塔結(jié)構(gòu):

按照這種分解寫的代碼,就像一本書,目錄和內(nèi)容清晰明了。

以商品上架為例,程序的入口是一個(gè)上架命令(OnSaleCommand), 它由三個(gè)階段(Phase)組成。

  1. @Command 
  2. public class OnSaleNormalItemCmdExe { 
  3.  
  4.     @Resource 
  5.     private OnSaleContextInitPhase onSaleContextInitPhase; 
  6.     @Resource 
  7.     private OnSaleDataCheckPhase onSaleDataCheckPhase; 
  8.     @Resource 
  9.     private OnSaleProcessPhase onSaleProcessPhase; 
  10.  
  11.     @Override 
  12.     public Response execute(OnSaleNormalItemCmd cmd) { 
  13.  
  14.         OnSaleContext onSaleContext = init(cmd); 
  15.  
  16.         checkData(onSaleContext); 
  17.  
  18.         process(onSaleContext); 
  19.  
  20.         return Response.buildSuccess(); 
  21.     } 
  22.  
  23.     private OnSaleContext init(OnSaleNormalItemCmd cmd) { 
  24.         return onSaleContextInitPhase.init(cmd); 
  25.     } 
  26.  
  27.     private void checkData(OnSaleContext onSaleContext) { 
  28.         onSaleDataCheckPhase.check(onSaleContext); 
  29.     } 
  30.  
  31.     private void process(OnSaleContext onSaleContext) { 
  32.         onSaleProcessPhase.process(onSaleContext); 
  33.     } 

每個(gè)Phase又可以拆解成多個(gè)步驟(Step),以O(shè)nSaleProcessPhase為例,它是由一系列Step組成的:

  1. @Phase 
  2. public class OnSaleProcessPhase { 
  3.  
  4.     @Resource 
  5.     private PublishOfferStep publishOfferStep; 
  6.     @Resource 
  7.     private BackOfferBindStep backOfferBindStep; 
  8.     //省略其它step 
  9.  
  10.     public void process(OnSaleContext onSaleContext){ 
  11.         SupplierItem supplierItem = onSaleContext.getSupplierItem(); 
  12.  
  13.         // 生成OfferGroupNo 
  14.         generateOfferGroupNo(supplierItem); 
  15.  
  16.        // 發(fā)布商品 
  17.         publishOffer(supplierItem); 
  18.  
  19.         // 前后端庫(kù)存綁定 backoffer域 
  20.         bindBackOfferStock(supplierItem); 
  21.  
  22.         // 同步庫(kù)存路由 backoffer域 
  23.         syncStockRoute(supplierItem); 
  24.  
  25.         // 設(shè)置虛擬商品拓展字段 
  26.         setVirtualProductExtension(supplierItem); 
  27.  
  28.         // 發(fā)貨保障打標(biāo) offer域 
  29.         markSendProtection(supplierItem); 
  30.  
  31.         // 記錄變更內(nèi)容ChangeDetail 
  32.         recordChangeDetail(supplierItem); 
  33.  
  34.         // 同步供貨價(jià)到BackOffer 
  35.         syncSupplyPriceToBackOffer(supplierItem); 
  36.  
  37.         // 如果是組合商品打標(biāo),寫擴(kuò)展信息 
  38.         setCombineProductExtension(supplierItem); 
  39.  
  40.         // 去售罄標(biāo) 
  41.         removeSellOutTag(offerId); 
  42.  
  43.         // 發(fā)送領(lǐng)域事件 
  44.         fireDomainEvent(supplierItem); 
  45.  
  46.         // 關(guān)閉關(guān)聯(lián)的待辦事項(xiàng) 
  47.         closeIssues(supplierItem); 
  48.     } 

看到了嗎,這就是商品上架這個(gè)復(fù)雜業(yè)務(wù)的業(yè)務(wù)流程。需要流程引擎嗎?不需要,需要設(shè)計(jì)模式支撐嗎?也不需要。對(duì)于這種業(yè)務(wù)流程的表達(dá),簡(jiǎn)單樸素的組合方法模式(Composed Method)是再合適不過的了。

因此,在做過程分解的時(shí)候,我建議工程師不要把太多精力放在工具上,放在設(shè)計(jì)模式帶來(lái)的靈活性上。而是應(yīng)該多花時(shí)間在對(duì)問題分析,結(jié)構(gòu)化分解,最后通過合理的抽象,形成合適的階段(Phase)和步驟(Step)上。

過程分解后的兩個(gè)問題

的確,使用過程分解之后的代碼,已經(jīng)比以前的代碼更清晰、更容易維護(hù)了。不過,還有兩個(gè)問題值得我們?nèi)リP(guān)注一下:

  • 領(lǐng)域知識(shí)被割裂肢解

什么叫被肢解?因?yàn)槲覀兊侥壳盀橹棺龅亩际沁^程化拆解,導(dǎo)致沒有一個(gè)聚合領(lǐng)域知識(shí)的地方。每個(gè)Use Case的代碼只關(guān)心自己的處理流程,知識(shí)沒有沉淀。

相同的業(yè)務(wù)邏輯會(huì)在多個(gè)Use Case中被重復(fù)實(shí)現(xiàn),導(dǎo)致代碼重復(fù)度高,即使有復(fù)用,最多也就是抽取一個(gè)util,代碼對(duì)業(yè)務(wù)語(yǔ)義的表達(dá)能力很弱,從而影響代碼的可讀性和可理解性。

  • 代碼的業(yè)務(wù)表達(dá)能力缺失

試想下,在過程式的代碼中,所做的事情無(wú)外乎就是取數(shù)據(jù)--做計(jì)算--存數(shù)據(jù),在這種情況下,要如何通過代碼顯性化的表達(dá)我們的業(yè)務(wù)呢?說實(shí)話,很難做到,因?yàn)槲覀內(nèi)笔Я四P?,以及模型之間的關(guān)系。脫離模型的業(yè)務(wù)表達(dá),是缺少韻律和靈魂的。

舉個(gè)例子,在上架過程中,有一個(gè)校驗(yàn)是檢查庫(kù)存的,其中對(duì)于組合品(CombineBackOffer)其庫(kù)存的處理會(huì)和普通品不一樣。原來(lái)的代碼是這么寫的:

  1. boolean isCombineProduct = supplierItem.getSign().isCombProductQuote(); 
  2.  
  3. // supplier.usc warehouse needn't check 
  4. if (WarehouseTypeEnum.isAliWarehouse(supplierItem.getWarehouseType())) { 
  5. // quote warehosue check 
  6. if (CollectionUtil.isEmpty(supplierItem.getWarehouseIdList()) && !isCombineProduct) { 
  7.     throw ExceptionFactory.makeFault(ServiceExceptionCode.SYSTEM_ERROR, "親,不能發(fā)布Offer,請(qǐng)聯(lián)系倉(cāng)配運(yùn)營(yíng)人員,建立品倉(cāng)關(guān)系!"); 
  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. if (sellableAmount < 1) { 
  19.     throw ExceptionFactory.makeFault(ServiceExceptionCode.SYSTEM_ERROR, "親,實(shí)倉(cāng)庫(kù)存必須大于0才能發(fā)布,請(qǐng)確認(rèn)已補(bǔ)貨.\r[id:" + supplierItem.getId() + "]"); 

然而,如果我們?cè)谙到y(tǒng)中引入領(lǐng)域模型之后,其代碼會(huì)簡(jiǎn)化為如下:

  1. if(backOffer.isCloudWarehouse()){ 
  2.     return
  3.  
  4. if (backOffer.isNonInWarehouse()){ 
  5.     throw new BizException("親,不能發(fā)布Offer,請(qǐng)聯(lián)系倉(cāng)配運(yùn)營(yíng)人員,建立品倉(cāng)關(guān)系!"); 
  6.  
  7. if (backOffer.getStockAmount() < 1){ 
  8.     throw new BizException("親,實(shí)倉(cāng)庫(kù)存必須大于0才能發(fā)布,請(qǐng)確認(rèn)已補(bǔ)貨.\r[id:" + backOffer.getSupplierItem().getCspuCode() + "]"); 

有沒有發(fā)現(xiàn),使用模型的表達(dá)要清晰易懂很多,而且也不需要做關(guān)于組合品的判斷了,因?yàn)槲覀冊(cè)谙到y(tǒng)中引入了更加貼近現(xiàn)實(shí)的對(duì)象模型(CombineBackOffer繼承BackOffer),通過對(duì)象的多態(tài)可以消除我們代碼中的大部分的if-else。

過程分解+對(duì)象模型

通過上面的案例,我們可以看到有過程分解要好于沒有分解,過程分解+對(duì)象模型要好于僅僅是過程分解。對(duì)于商品上架這個(gè)case,如果采用過程分解+對(duì)象模型的方式,最終我們會(huì)得到一個(gè)如下的系統(tǒng)結(jié)構(gòu):

寫復(fù)雜業(yè)務(wù)的方法論

通過上面案例的講解,我想說,我已經(jīng)交代了復(fù)雜業(yè)務(wù)代碼要怎么寫:即自上而下的結(jié)構(gòu)化分解+自下而上的面向?qū)ο蠓治觥?/p>

接下來(lái),讓我們把上面的案例進(jìn)行進(jìn)一步的提煉,形成一個(gè)可落地的方法論,從而可以泛化到更多的復(fù)雜業(yè)務(wù)場(chǎng)景。

上下結(jié)合

所謂上下結(jié)合,是指我們要結(jié)合自上而下的過程分解和自下而上的對(duì)象建模,螺旋式的構(gòu)建我們的應(yīng)用系統(tǒng)。這是一個(gè)動(dòng)態(tài)的過程,兩個(gè)步驟可以交替進(jìn)行、也可以同時(shí)進(jìn)行。

這兩個(gè)步驟是相輔相成的,上面的分析可以幫助我們更好的理清模型之間的關(guān)系,而下面的模型表達(dá)可以提升我們代碼的復(fù)用度和業(yè)務(wù)語(yǔ)義表達(dá)能力。

其過程如下圖所示:

使用這種上下結(jié)合的方式,我們就有可能在面對(duì)任何復(fù)雜的業(yè)務(wù)場(chǎng)景,都能寫出干凈整潔、易維護(hù)的代碼。

能力下沉

一般來(lái)說實(shí)踐DDD有兩個(gè)過程:

  • 套概念階段:了解了一些DDD的概念,然后在代碼中“使用”Aggregation Root,Bounded Context,Repository等等這些概念。更進(jìn)一步,也會(huì)使用一定的分層策略。然而這種做法一般對(duì)復(fù)雜度的治理并沒有多大作用。
  • 融會(huì)貫通階段:術(shù)語(yǔ)已經(jīng)不再重要,理解DDD的本質(zhì)是統(tǒng)一語(yǔ)言、邊界劃分和面向?qū)ο蠓治龅姆椒ā?/li>

大體上而言,我大概是在1.7的階段,因?yàn)橛幸粋€(gè)問題一直在困擾我,就是哪些能力應(yīng)該放在Domain層,是不是按照傳統(tǒng)的做法,將所有的業(yè)務(wù)都收攏到Domain上,這樣做合理嗎?說實(shí)話,這個(gè)問題我一直沒有想清楚。

因?yàn)樵诂F(xiàn)實(shí)業(yè)務(wù)中,很多的功能都是用例特有的(Use case specific)的,如果“盲目”的使用Domain收攏業(yè)務(wù)并不見得能帶來(lái)多大的益處。相反,這種收攏會(huì)導(dǎo)致Domain層的膨脹過厚,不夠純粹,反而會(huì)影響復(fù)用性和表達(dá)能力。

鑒于此,我最近的思考是我們應(yīng)該采用能力下沉的策略。

所謂的能力下沉,是指我們不強(qiáng)求一次就能設(shè)計(jì)出Domain的能力,也不需要強(qiáng)制要求把所有的業(yè)務(wù)功能都放到Domain層,而是采用實(shí)用主義的態(tài)度,即只對(duì)那些需要在多個(gè)場(chǎng)景中需要被復(fù)用的能力進(jìn)行抽象下沉,而不需要復(fù)用的,就暫時(shí)放在App層的Use Case里就好了。

注:Use Case是《架構(gòu)整潔之道》里面的術(shù)語(yǔ),簡(jiǎn)單理解就是響應(yīng)一個(gè)Request的處理過程。

通過實(shí)踐,我發(fā)現(xiàn)這種循序漸進(jìn)的能力下沉策略,應(yīng)該是一種更符合實(shí)際、更敏捷的方法。因?yàn)槲覀兂姓J(rèn)模型不是一次性設(shè)計(jì)出來(lái)的,而是迭代演化出來(lái)的。

下沉的過程如下圖所示,假設(shè)兩個(gè)use case中,我們發(fā)現(xiàn)uc1的step3和uc2的step1有類似的功能,我們就可以考慮讓其下沉到Domain層,從而增加代碼的復(fù)用性。

指導(dǎo)下沉有兩個(gè)關(guān)鍵指標(biāo):

  • 復(fù)用性
  • 內(nèi)聚性

復(fù)用性是告訴我們When(什么時(shí)候該下沉了),即有重復(fù)代碼的時(shí)候。內(nèi)聚性是告訴我們How(要下沉到哪里),功能有沒有內(nèi)聚到恰當(dāng)?shù)膶?shí)體上,有沒有放到合適的層次上(因?yàn)镈omain層的能力也是有兩個(gè)層次的,一個(gè)是Domain Service這是相對(duì)比較粗的粒度,另一個(gè)是Domain的Model這個(gè)是最細(xì)粒度的復(fù)用)。

比如,在我們的商品域,經(jīng)常需要判斷一個(gè)商品是不是最小單位,是不是中包商品。像這種能力就非常有必要直接掛載在Model上。

  1. public class CSPU { 
  2.     private String code; 
  3.     private String baseCode; 
  4.     //省略其它屬性 
  5.  
  6.     /** 
  7.      * 單品是否為最小單位。 
  8.      * 
  9.      */ 
  10.     public boolean isMinimumUnit(){ 
  11.         return StringUtils.equals(code, baseCode); 
  12.     } 
  13.  
  14.     /** 
  15.      * 針對(duì)中包的特殊處理 
  16.      * 
  17.      */ 
  18.     public boolean isMidPackage(){ 
  19.         return StringUtils.equals(code, midPackageCode); 
  20.     } 

之前,因?yàn)槔舷到y(tǒng)中沒有領(lǐng)域模型,沒有CSPU這個(gè)實(shí)體。你會(huì)發(fā)現(xiàn)像判斷單品是否為最小單位的邏輯是以StringUtils.equals(code, baseCode)的形式散落在代碼的各個(gè)角落。這種代碼的可理解性是可想而知的,至少我在第一眼看到這個(gè)代碼的時(shí)候,是完全不知道什么意思。

業(yè)務(wù)技術(shù)要怎么做

寫到這里,我想順便回答一下很多業(yè)務(wù)技術(shù)同學(xué)的困惑,也是我之前的困惑:即業(yè)務(wù)技術(shù)到底是在做業(yè)務(wù),還是做技術(shù)?業(yè)務(wù)技術(shù)的技術(shù)性體現(xiàn)在哪里?

通過上面的案例,我們可以看到業(yè)務(wù)所面臨的復(fù)雜性并不亞于底層技術(shù),要想寫好業(yè)務(wù)代碼也不是一件容易的事情。業(yè)務(wù)技術(shù)和底層技術(shù)人員唯一的區(qū)別是他們所面臨的問題域不一樣。

業(yè)務(wù)技術(shù)面對(duì)的問題域變化更多、面對(duì)的人更加龐雜。而底層技術(shù)面對(duì)的問題域更加穩(wěn)定、但對(duì)技術(shù)的要求更加深。比如,如果你需要去開發(fā)Pandora,你就要對(duì)Classloader有更加深入的了解才行。

但是,不管是業(yè)務(wù)技術(shù)還是底層技術(shù)人員,有一些思維和能力都是共通的。比如,分解問題的能力,抽象思維,結(jié)構(gòu)化思維等等。

用我的話說就是:“做不好業(yè)務(wù)開發(fā)的,也做不好技術(shù)底層開發(fā),反之亦然。業(yè)務(wù)開發(fā)一點(diǎn)都不簡(jiǎn)單,只是我們很多人把它做“簡(jiǎn)單”了。

因此,如果從變化的角度來(lái)看,業(yè)務(wù)技術(shù)的難度一點(diǎn)不遜色于底層技術(shù),其面臨的挑戰(zhàn)甚至更大。因此,我想對(duì)廣大的從事業(yè)務(wù)技術(shù)開發(fā)的同學(xué)說:沉下心來(lái),夯實(shí)自己的基礎(chǔ)技術(shù)能力、OO能力、建模能力... 不斷提升抽象思維、結(jié)構(gòu)化思維、思辨思維... 持續(xù)學(xué)習(xí)精進(jìn),寫好代碼。我們可以在業(yè)務(wù)技術(shù)崗做的很”技術(shù)“!。

后記

這篇文章是我最近思考的一些總結(jié),大部分思想是繼承自我原來(lái)寫的COLA架構(gòu),該架構(gòu)已經(jīng)開源,目前在集團(tuán)內(nèi)外都有比較廣泛的使用。

這一篇主要是在COLA的基礎(chǔ)上,針對(duì)復(fù)雜業(yè)務(wù)場(chǎng)景,做了進(jìn)一步的架構(gòu)落地。個(gè)人感覺可以作為COLA的最佳實(shí)踐來(lái)使用。

另外,本文討論的問題之大和篇幅之短是不成正比的。原因是我假定你已經(jīng)了解了一些DDD和應(yīng)用架構(gòu)的基礎(chǔ)知識(shí)。如果覺得在理解上有困難,我建議可以先看下《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》和《架構(gòu)整潔之道》這兩本書。

如果沒有那么多時(shí)間,也可以快速瀏覽下我之前的兩篇文章應(yīng)用架構(gòu)之道 和 領(lǐng)域建模去知曉一下我之前的思想脈絡(luò)。

責(zé)任編輯:武曉燕 來(lái)源: 阿里技術(shù)
相關(guān)推薦

2020-10-12 07:57:42

技術(shù)架構(gòu)制圖

2020-09-27 14:24:58

if-else cod業(yè)務(wù)

2023-05-30 07:56:23

代碼軟件開發(fā)

2013-12-25 09:50:27

華為馬悅企業(yè)業(yè)務(wù)

2022-07-04 19:02:06

系統(tǒng)業(yè)務(wù)思考

2022-06-27 08:47:29

BEM修飾符元素

2021-11-05 08:28:27

內(nèi)存泄漏調(diào)試

2014-12-15 17:36:51

2018-04-12 09:46:12

DevOps運(yùn)維建設(shè)

2014-09-11 15:05:40

驅(qū)動(dòng)設(shè)計(jì)驅(qū)動(dòng)開發(fā)

2017-06-05 15:08:14

容量全鏈路流量

2018-10-08 09:00:58

考核技術(shù)人KPI

2023-07-17 18:39:27

業(yè)務(wù)系統(tǒng)架構(gòu)

2020-02-28 11:13:35

辦公阿里周報(bào)

2010-01-04 10:07:03

程序員

2022-03-14 22:22:56

工程設(shè)計(jì)論代碼

2022-04-07 17:30:31

Flutter攜程火車票渲染

2017-04-21 07:41:37

iOS自動(dòng)化測(cè)試容器

2020-04-13 13:13:20

NLPAI語(yǔ)音

2023-02-22 08:15:13

壓測(cè)模擬計(jì)算
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)