有贊訂單導出業務隸屬於有贊交易訂單管理組,主要職能是將有贊商家的訂單數據經過報表的形式導出並提供下載給商家使用。目前承接了有贊全部的訂單導出業務,報表的字段覆蓋交易、支付、會員、優惠、發貨、退款、特定業務等,合計超過 100 個。java
隨着有讚的迅速發展,有讚的行業、業務與產品覆蓋面愈來愈廣。從行業角度來看,覆蓋了微商城、新零售、餐飲、美業、教育等,從模塊角度來看,覆蓋了交易、資產、客戶、營銷、店鋪等,從產品角度來看,覆蓋了分銷、精選等。每一個行業、模塊、產品都會在訂單導出報表中有所訴求。以下圖所示,展現了有贊訂單導出的域模型:數據庫
訂單導出須要跨越來自不一樣行業、不一樣產品、不一樣模塊,對各個業務域的存儲和設計有總體理解;同時,須要經過技術手段(數據域、存儲域、報表域、文件域)聚合來自各個域的數據集合,生成可讀的報表下載給商家。編程
因而可知,其主要的挑戰是:如何快速支持各個域靈活多變的導出字段需求。如何應對這一挑戰呢?設計模式
訂單導出的最初實現是從交易的多個 DB 及多個業務 API,分別獲取交易、支付、會員、發貨、退款、覈銷、分銷等多個數據,組裝到一塊兒生成報表。採用 PHP 任務腳原本實現。這種作法有兩個痛點:緩存
基於這兩個痛點,有贊訂單管理組進行了架構升級,詳見有贊技術博文《有贊訂單管理的三生三世與「十面埋伏》。 得益於此,訂單導出也遷移到基於 ES + Hbase 的技術棧。其中訂單搜索採用 ES 服務實現,訂單詳情則存儲在 Hbase 中,經過 API 來獲取。總體流程以下所示:架構
重構以後,訂單導出的性能和穩定性有了很大的提高:併發
接下來,開始了配置化之旅。函數
訂單導出經常要面臨添加新的報表字段的需求。最初實現不太靈活,是來一個字段,在代碼流程裏添加一個字段。每次增長新的字段,都須要修改多處。所以,第一個優化是採用函數接口編程,將字段定義作成枚舉可配置化的,而後遍歷指定的報表字段列表,拿到對應的字段定義,計算字段的值,寫入報表文件。 工具
根據報表字段列表生成報表行的僞代碼以下:post
public List<String> generateReportLineData(List<String> fields) { return StreamUtil.map(fields, field -> { try { FieldDefinition fieldDef = getFieldDefinition(field); FieldMethod method = getMethod(fieldDef); String value = method.invoke(this.reportItem); return postproc(value); }catch (Exception e){ logger.warn("failed to get value for field: {} orderNo: {}", field, reportItem.getOrderNo()); return ""; } }); }
這個小小的優化,爲進一步的配置化設下伏筆。當須要新增報表字段時,只要增長新的字段定義,而不須要在流程裏增長代碼。加強軟件可擴展性的一個重要方法是,將流程變得通用,只要增刪流程裏的環節及定義便可。
凡基礎必要老是正確的方向。
有贊新零售、餐飲的迅速興起和發展,須要低成本快速地搭建起零售和餐飲的訂單導出。這要求訂單導出具備更大的靈活性,可以根據不一樣行業的要求配置不一樣的字段列表及導出格式,同時又能互不影響。此外,不一樣商家有個性化的導出需求。然而,原來的訂單導出,是專門爲微商城開發的商品級別的報表。要加一個字段,每每會影響全部的有贊商家,使用體驗不佳,訂單報表自己也變得臃腫不堪。
如何突破原來的侷限,支持更靈活的訂單導出呢?這是訂單導出面臨的一個破局點。經過訂單導出模板解決了這個問題。針對行業、產品配置的導出模板存儲在 DB 表 export_biz_conf 裏;針對有贊商家的導出模板存儲在 DB 表 export_customized_conf 裏。每一個導出模板包括了以下信息:報表字段列表、導出維度(訂單及商品)、報表文件格式、可選項等,作到足夠靈活。
若要導出不一樣報表字段,只要新增相應字段,指定報表字段列表便可;若要生成不一樣維度的報表,可以使用策略模式。好比,
如圖所示: 針對導出流程的各環節,可採用策略模式來選擇不一樣實現,而後將策略組合起來。
經過實現報表配置功能,突破了以前的侷限,能夠支持不一樣行業、產品的標準化和定製化導出需求,而且作到相互隔離不干擾。
隨着有贊進入更多的行業,面臨着更加多變和個性化的導出需求。好比,有贊教育須要導出知識訂單的學員信息和課程信息,有贊零售須要導出導購員和發貨倉庫門店名稱。 顯然,若是要完成某個導出需求,還須要修改代碼、發佈系統,這種操做會很是頻繁,致使開發和維護成本提高,影響系統穩定性。
若是可以在應用運行中動態地新增報表字段並加載和使用,無需修改導出工程代碼,無需從新發布系統,就能更加快速地支持導出需求,將會大幅下降導出需求支持的開發和維護成本,保持系統穩定性。
爲了解決這個問題,引入了動態腳本語言 Groovy. Groovy 是可以與 Java 無縫對接的好夥伴,能夠直接使用 Java 類的功能。編寫 Groovy 腳本實現報表字段邏輯,存儲在字段配置表 export_field_conf 裏, 在報表配置表 export_biz_conf 或 export_customized_conf 裏引用,而後在應用啓動時緩存到內存裏並使用。好比粉絲姓名的 Groovy 腳本以下:
import com.youzan.trade.orderexport.util.PublicUtil def fansInfo = reportItem.orderInfo.extra["FANS"] PublicUtil.fetch(fansInfo, "nickname")
PublicUtil 是導出工程裏封裝的一個工具類,可讓編寫字段配置腳本更加簡單。值得說起的是,爲了不使用 Groovy 腳本可能致使的內存泄露,須要對編譯後的 Groovy 腳本進行緩存和執行。
爲了實現無需改動代碼和發佈系統,還須要在總體流程上打通。總體流程以下:
Step1: 當用戶下單後,源數據落到業務數據庫的擴展信息裏;
Step2: 經過數據同步,自動同步到 Hbase 表;
Step3: 經過 Apollo 配置和可擴展的數據聚合機制,將數據自動輸送到用來計算報表字段值的報表對象裏;
Step4: 新增報表字段的配置;
Step5: 在報表配置中引用該字段的標識。
下圖展現了經過配置自定義字段快速支持導出需求的總體流程。
總體流程打通後,當須要新增個性化字段時,一般只要作兩步:
個性化字段配置能力已經在線上穩定運行,好比拼團訂單成團時間、零售導購員、有贊教育的課程字段等。
緊接着,訂單導出又面臨分銷採購單的導出需求。分銷採購單導出流程跟訂單導出有所不一樣,須要分別導出分銷買家訂單和供貨訂單的詳情信息再導出。這個流程跟通用的訂單導出流程是有所區別的。若是經過修改訂單導出的通用流程來支持,顯然會影響全部的訂單導出,使訂單導出流程不清晰。
最終採用的解決方案是:對分銷採購單的導出需求和所需技術進行抽象,實現一個更加通用的導出能力模型,支持交易領域的各類潛在導出需求,而不只僅侷限於分銷採購單導出。經過分析訂單導出流程能夠發現,絕大多數導出都遵循以下核心流程:
能夠將核心流程作成插件式的。首先,定義一個插件接口,包含其配置和功能等;其次,實現經常使用的插件列表,支持從 ES, HBase, API 查詢或獲取數據,以及經常使用的過濾、排序、格式化、生成報表等功能;最後,將這些插件列表串聯成一個具體的導出實例。總體流程則採用模板方法模式複用了訂單導出流程。
好比微商城分銷採購單導出經過依次執行ES查詢插件、訂單詳情插件、數據排序插件、報表字段格式化插件、報表生成插件來實現,其中訂單詳情插件針對分銷買家單和供貨訂單分別調用了一次。
前面提到,訂單導出的報表字段很是多,導出數據量大,如何保證代碼改動或重構後訂單導出的服務質量和數據準確性?主要手段以下:
此外,採用函數編程及設計模式,使代碼實現層面更具複用性和柔軟性。18K 行代碼,代碼重複率約爲 1.8%。
本文簡要講述了有贊訂單導出的配置化實踐。經過配置化以後,訂單導出的能力和穩定性有了大幅提高。固然,還有一些須要提高的地方。好比,能夠增長擴展點機制,容許業務方定製化導出;局部細節能夠打磨得更細膩。歡迎對海量訂單業務感興趣有經驗的小夥伴與咱們一塊兒共建訂單管理大局!簡歷可直郵 shuqin@youzan.com.
在這個過程當中,有許多小夥伴給予了有力的支持,好比產品同窗對訂單報表的細緻的規劃設計、客滿運營同窗提出的及時反饋、有贊技術團隊的支持以及本身的付出。