天貓優品導購歸因鏈路負責天貓優品訂單導購斷定工做,目前支撐了天貓優品權益券導購、普通導購和淘花導購等多種導購類型。 隨着業務迭代,現有導購歸因鏈路在維護性、擴展性和可讀性等方面存在明顯不足,代碼複雜性不斷攀升,歷史代碼債務逐步積累。
爲解決上述問題,開展了天貓優品導購歸因鏈路技術重構工做。進一步地,在導購歸因重構基礎上,做爲對「屬性-分類-執行」問題的產品化思考與實踐,提出了一種通用歸因技術組件 ACE 。ACE 組件基於屬性校驗器、分類器和執行器三層模型解決了屬性分類的通用性問題,具備良好的擴展性和代碼語義。java
本文以天貓優品導購歸因重構爲背景,闡述了一種基於 ACE 組件的訂單歸類技術方案。web
技術痛點編程
伴隨業務快速上下線,現有天貓優品導購歸因鏈路在不斷迭代過程當中逐漸積累歷史代碼債務,應用代碼存在複雜性高、擴展性低、可讀性差等問題。
微信
▐ 事務腳本編程
事務腳本編程致使代碼複雜性攀升。事務腳本型代碼可能咱們天天都在寫,咱們有時在用一門面向對象語言寫着面向過程代碼,基於 IF-ELSE 等條件判斷語句快速堆砌業務代碼。每新增一行業務代碼,也許就新增了一行代碼債務,應用代碼複雜性逐步攀升。
架構
圖 1 :代碼複雜性的演變app
▐ 違背開閉原則
違背開閉原則致使可維護性差。每次業務需求迭代,都在原有業務代碼基礎上修改或新增邏輯。咱們很難知道歷史代碼哪些地方埋了坑,最好的方式就是儘可能避免改動它。實際上,對於大部分業務代碼,很難保證在新增需求時徹底不須要改動原有代碼邏輯。框架
▐ 缺失架構設計
缺失架構設計致使可擴展性差。業務型技術團隊經常面臨業務需求急、開發週期短等問題,爲支撐新業務快速上線,有時會採起最快的方式知足業務訴求。然而,最快的方式每每缺失架構設計,只爲知足單一需求,對後續迭代並不友好。隨着源源不斷的新需求,應用代碼很快陷入破窗效應,可擴展性愈來愈差,代碼債務不斷積累。編輯器
▐ 業務邏輯複雜
複雜業務邏輯致使代碼可讀性差。看到下面這段代碼,可能很難理解知足哪些屬性是導購訂單。這樣的代碼在業務型技術團隊很常見,咱們帶着業務需求打開應用代碼,卻發現連原有代碼所表示的業務含義都難以理解。代碼可讀性對業務型技術團隊尤其重要,由於代碼每每隱藏着業務含義,複雜的業務場景加上晦澀難懂的應用代碼無疑是雪上加霜。ide
圖 2 :晦澀難懂的業務代碼邏輯優化
重構目標
爲解決上述技術痛點,結合天貓優品導購業務發展背景,制定如下重構目標。
▐ 精簡導購歸因鏈路,清理過期業務邏輯
業務發展存在不斷試錯的過程,應用代碼伴隨着業務不斷迭代。有些業務代碼雖然早已過期,且因爲團隊開發人員流動,誰也不敢輕易刪除歷史代碼。代碼上線容易下線難,代碼愈發臃腫。所以,有必要精簡現有天貓優品導購歸因邏輯,清理過期業務邏輯。
▐ 抽象業務模型,向後兼容業務發展
結合現有業務場景,抽象業務模型,支持後續業務輕量化迭代。經過抽象業務模型,可下降應用代碼複雜度與業務場景複雜度的強相關性,甚至實現同一模型支撐多種不一樣業務場景。
▐ 完善業務優先級決策,規則統一收口
規範業務優先級決策,統一收口業務優先級規則,便於後續代碼維護和業務迭代。優先級決策是一種很常見的業務規則。如何用一行代碼描述全部業務優先級,而不是將業務優先級判斷散落在應用的多處地方?
▐ 提高代碼可讀性,代碼語義即業務語義
藉助通用業務模型,賦予代碼更豐富的語義,提高代碼可讀性。代碼可讀性對業務型技術團隊尤爲重要,看懂代碼即看懂業務規則,可極大減小溝通成本,提高開發效率。
技術方案
基於現有技術痛點和重構目標,首先抽象業務模型,而後設計了一種通用歸因技術組件 ACE,最後將 ACE 應用於天貓優品導購歸因鏈路。
▐ 業務模型
以導購歸由於例,導購歸因旨在判斷一個訂單存在哪一種類型的有效導購行爲。有效性定義可歸納爲兩個方面:一是知足或過濾某些屬性,二是知足業務優先級規則。
導購歸因是訂單歸類和優先級決策的組合,具體歸納爲如下四個步驟:
Step 1 :導購訂單必須知足或不知足某些屬性
例如,天貓優品導購訂單必須知足天貓優品商品等屬性,且不知足(過濾)本地履約訂單等屬性。
Step 2 :不一樣屬性組合成不一樣類型導購訂單
例如,權益券導購訂單 = 天貓優品商品訂單 + 權益券訂單 + ... + 非本地履約訂單 + 非定向優惠訂單。
Step 3 :不一樣導購訂單類型存在不一樣業務優先級
根據優先級規則決策哪一種類型導購訂單有效。例如,權益券導購訂單優先級高於普通導購訂單。
Step 4 :根據歸因結果執行不一樣處理流程
例如,訂單斷定爲導購訂單,執行落庫、打標、消息推送等流程。
進一步地,導購歸因可抽象爲「屬性-分類-執行」問題,抽象模型以下:
屬性校驗器(Attributor):表示一種屬性。校驗是否知足某個屬性,支持原子或組合屬性。
分類器(Classifier):表示一種類型。綁定一個或多個屬性校驗器,校驗是否知足某些屬性組合。分類器可分爲嵌套分類器(NestedClassifier)和原子分類器(AtomicClassifier)。例如,Classifier 1 須要知足多個 Attributor,Classifier 4 須要知足 Classifier 1 和 Classifier 2。
執行器(Executor):表示一種類型對應的執行策略。綁定一個分類器,負責對某種類型執行處理。
圖 3 :模型層次結構 Attributor-Classifier-Executor
▐ 歸因組件
基於現有業務場景,抽象了一種「屬性-分類-執行」的技術模型。在通用模型基礎上,設計了一種歸因組件 ACE 。ACE 是 Attributor - Classifier - Executor 的縮寫,旨在經過屬性校驗器(Attributor)、分類器(Classifier)和執行器(Executor)三層模型解決屬性分類的通用性問題。
總體設計
ACE 對外暴露統一服務接口 AceWorker,AceWorker 接收外部傳入參數(歸因場景 + 歸因對象),根據歸因場景獲取分類器,並判斷歸因對象是否知足該分類器。分類器是 ACE 的核心,綁定了一個或多個屬性校驗器,並對應惟一的執行器。
圖 4 :ACE 總體設計
詳細設計
ACE 組件由 ACE 註解、ACE 工廠容器、ACE 初始化和 ACE 服務入口組成,詳細設計如圖 5 所示。
ACE 註解
基於易用性考慮,ACE 提供三種註解 @Attributor、@Classifier 和 @Executor 用於聲明 ACE 組件,分別對應屬性校驗器、分類器和執行器。
@Attributor:聲明一個屬性校驗器,屬性校驗器名稱惟一。
@Classifier:聲明一個分類器,分類器名稱惟一。@Classifier 提供 matcher、filter 和 priority 三種屬性,matcher 用於指定該分類器需知足的屬性校驗器列表,filter 用於指定該分類器需過濾的屬性校驗器列表,priority 用於指定該分類器綁定的原子分類器的優先級規則。
@Executor:聲明一個執行器,每一個分類器對應一個執行器,執行器名稱需與分類器名稱一致。
ACE 工廠容器
AceFactory 是 ACE 的工廠容器,負責管理全部定義的 ACE 組件,包括屬性校驗器集合、分類器集合及其綁定的屬性校驗器集合、執行器集合。根據 ACE 組件名稱可直接從 AceFactory 獲取對應的 ACE 組件。
ACE 初始化
藉助 AceInitService 初始化 ACE 組件,應用啓動時 AceInitService 自動解析 ACE 註解,並將 ACE 組件註冊到 ACE 工廠容器。
ACE 服務入口
AceWorker 是 ACE 的服務入口,負責對外提供 ACE 通用服務,如屬性校驗 attribute、分類 classify 和執行 execute 。
圖 5 :ACE 詳細設計
示例
1)定義屬性校驗器
定義屬性校驗器 A ,判斷是否知足屬性 A 。
/** * 屬性校驗器示例 ATTRIBUTOR_A * @author haoyu.chy * @date 2020/9/5 */"ATTRIBUTOR_A") (name = public class AttributorA implements IAttributor { public AceResult attribute(AceContext aceContext) { if (知足屬性A) { return new AceResult(true); } return new AceResult(false); }}
2)定義分類器
定義原子分類器 CLASSIFIER_X(綁定屬性校驗器 ATTRIBUTOR_A ),斷定是否知足類型 X 。
/** * 原子分類器示例 * matcher:需匹配的屬性 * filter:需過濾的屬性 * @author haoyu.chy * @date 2020/9/5 */"CLASSIFIER_X", matcher = "ATTRIBUTOR_A", filter = "") (name = public class ClassifierX implements AtomicClassifier {
}
定義嵌套分類器 CLASSIFIER_NEST ,綁定原子分類器 CLASSIFIER_X 和 CLASSIFIER_Y ,CLASSIFIER_X 優先級高於 CLASSIFIER_Y 。
/** * 嵌套分類器示例 * priority:表示綁定的原子分類器的優先級規則 * @author haoyu.chy * @date 2020/9/5 */"CLASSIFIER_NEST", priority = "CLASSIFIER_X,CLASSIFIER_Y") (name = public class Classifier_Nest implements NestedClassifier {
}
3)定義執行器
定義原子分類器 CLASSIFIER_X 對應的執行器 ExecutorX 。
/** * 執行器示例 * 注:執行器名稱對應分類器名稱 * @author haoyu.chy * @date 2020/9/5 */"CLASSIFIER_X") (name = public class ExecutorX implements IExecutor { public AceResult execute(AceContext aceContext) { // do something ... return new AceResult(); }}
4)定義服務入口
定義歸因服務入口,指定歸因場景 aceScene 和歸因對象 aceObject,藉助 AceWorker 完成歸因工做。
/** * 歸因服務入口示例 * @author haoyu.chy * @date 2020/9/5 */public class SimpleService { public static void main(String[] args) { // 歸因場景(例如,CLASSIFIER_NEST) String aceScene = "CLASSIFIER_NEST"; // 被歸因對象(例如,訂單號) Long aceObject = 1234567890L; // 初試化上下文 AceContext<Long> aceContext = AceContext.of(aceScene, aceObject); // 執行歸因流程 AceResult aceResult = AceWorker.getInstance().classify(aceContext); }}
▐ 天貓優品導購歸因
天貓優品導購訂單類型舉例:
權益券導購訂單:訂單存在權益券使用記錄
天貓優品普通導購訂單:天貓優品商品且最近一次導購記錄爲普通導購
天貓優品淘花導購訂單:天貓優品商品且最近一次導購記錄爲淘花導購
不一樣類型訂單有不一樣優先級,業務規則以下:
優先級規則:權益券導購優先級最高,普通導購和淘花導購優先級並列(最近原則)
互斥規則:天貓優品定向優惠訂單、本地履約訂單、代購訂單優先級高於全部類型導購訂單
基於 ACE 組件,重構天貓優品導購歸因技術鏈路,設計天貓優品導購歸因流程如圖 6 。
圖 6 :天貓優品導購歸因流程
Step 1 :定義屬性校驗器
屬性校驗器相互獨立,表示是否知足某種屬性。例如,定義屬性校驗器(Attributor):天貓優品商品標、權益導購券、普通導購記錄、淘花導購記錄、定向優惠、本地履約等。
Step 2:定義原子分類器
原子分類器綁定多個屬性校驗器,表示是否知足某種類型。例如,定義分類器(Classifier):權益券導購訂單(COUPON_GUIDE)、普通導購訂單(NORMAL_GUIDE)和淘花導購訂單(SUPERB_GUIDE)。其中,權益券導購訂單(COUPON_GUIDE)綁定權益導購券屬性,過濾定向優惠和本地履約等屬性。
注:圖 6 實線表示知足,虛線表示過濾
Step 3:定義嵌套分類器
嵌套分類器綁定多個原子分類器,通常用於優先級決策場景,按先後順序匹配第一個有效分類器。例如,定義嵌套分類器(GUIDE_ORDER),其綁定原子分類器(COUPON_GUIDE、NORMAL_GUIDE 和 SUPERB_GUIDE),優先級從前日後。
Step 4:定義執行器
每一個原子分類器對應一個執行器。若是某個分類器有效,則執行對應的執行器。
Step 5:定義服務入口
定義 ACE 組件後,只需指定歸因場景(對應分類器名稱)和歸因對象,便可使用歸因服務。例如,歸因場景爲導購歸因(GUIDE_ORDER),歸因對象爲某個訂單,則表示對某個訂單執行導購歸因。
基於 ACE 組件,定義屬性校驗器、分類器和執行器,封裝服務接口。天貓優品導購歸因代碼框架如圖 7 。
圖 7 :天貓優品導購歸因代碼框架
效果分析
▐ 遵循 SOLID 原則
單一責任原則(SRP):每一個 ACE 組件只負責一種職責。Attributor 只判斷是否知足某種屬性,Classifier 只判斷是否知足某種類型,Executor 只對某種類型執行處理。
開放關閉原則(OCP):ACE 組件之間相互獨立。新增屬性或分類無需修改原有組件,只需定義一種新的屬性校驗器或分類器便可,徹底無需改動原有代碼。
里氏替換原則(LSP):父類可用子類替代,子類只擴展父類方法,不重寫父類方法。ACE 基於接口實現,不重寫已實現方法。
接口隔離原則(ISP):子類不被迫依賴它不須要的方法。ACE 組件接口相互獨立,且只提供惟一方法。
依賴倒置原則(DIP):ACE 組件面向接口編程,基於 ACE 工廠容器實現依賴注入,組件間不存在直接依賴關係。
舉例:
如圖 8 所示,新增導購類型(優盟導購),只需新增分類器 Classifier(UM_GUIDE),並綁定相關屬性 Attributor(優盟),關聯執行器 Executor(優盟導購)。藉助 ACE 組件可無侵入性地新增業務類型,徹底無需改動原有代碼。
圖 8 :新增導購類型(優盟導購)
▐ 代碼結構優化
代碼結構從原有的「縱向+多出口」轉變成「橫向+單出口」。從代碼維護角度,單出口程序更利於維護,代碼(方法)出口統一收口到一處地方,代碼邏輯一目瞭然。
圖 9 :橫向單出口的代碼結構
總結
本文提出了一種通用歸因技術組件 ACE,並將其應用於天貓優品導購歸因技術鏈路。ACE 組件以屬性校驗器、分類器和執行器三層模型爲核心,規範化定義某種類型需知足的屬性組合及其對應的執行策略。
ACE 組件借鑑了策略模式思想,不一樣分類器對應不一樣執行器(策略),但只有分類器有效時,相應執行器(策略)纔會被執行。分類器支持組裝式關聯多個屬性校驗器,同一屬性校驗器可被多個分類器複用,具備較好的靈活性和擴展性。此外,ACE 組件可將代碼邏輯結構化,提高應用代碼的可讀性。
思考
一週歲技術新人的非嚴謹思考
「業務需求的局部性原理」
你們都聽過計算機系統的局部性原理,其實業務需求也存在「局部性原理」。小到多打一行日誌、多傳一個參數,大到多留一個擴展點、抽象一個服務,這都在爲後續需求留餘地。寫代碼時多作一步,不寫一次性代碼,也許反而能減小後續工做量。
「應用代碼的破窗效應」
在實際需求開發過程當中,咱們每每會參考原有代碼實現。代碼風格或結構設計是具備傳染性的,糟糕的代碼風格和架構設計會使應用陷入破窗效應。一個不成熟的思考,是否絕大部分應用都難逃破窗效應?即便前期有較好的架構設計,但因爲業務發展和人員流動,原有架構約束依然有可能過期或被忽略。
「業務團隊的技術挑戰」
在集團的「關懷」下,業務型技術團隊的技術越作越輕,業務越作越重。開箱即用的中間件,讓業務團隊變得「沒有技術挑戰」。剛參加工做的這一年,經常焦慮我的技術成長。回過頭想,對於業務團隊而言,技術挑戰也許在廣不在深。大多數狀況下,技術型團隊或許在和機器打交道,考驗技術深度與鑽研能力;業務型團隊則在和商業打交道,考驗技術架構與抽象能力。孰好孰壞彷佛沒有絕對答案。
✿ 拓展閱讀
本文分享自微信公衆號 - 淘系技術(AlibabaMTT)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。