閒魚策略中樞業務擴充套件模組實現

語言: CN / TW / HK

前言

擴充套件能力是我們在做平臺、中臺時都會面臨的技術思考。 為了平臺能儘可能覆蓋更多的業務場景,我們需要設計可擴充套件,可複用的擴充套件模組,不斷拓寬平臺能力的邊界。 本文介紹閒魚策略中樞平臺Luxury做業務擴充套件模組時遇到的問題以及解決思路,希望能為同樣做平臺擴充套件模組的讀者提供一些啟發。

背景

閒魚策略中樞Luxury是面向閒魚全域業務的使用者精細化策略運營平臺,旨在引導使用者認識、參與閒魚的各業務場景、精準的傳遞平臺價值。

以下面的紅包投放為例,它就是Luxury的一條投放策略投放到客戶端上之後的展現形式。

一條投放策略包含了:

  • 規則(策略觸發時機)。對應紅包例子,觸發時機就是使用者開啟客戶端首頁時識別有未使用紅包

  • 觸點(投放的素材樣式)。對應紅包例子,素材樣式就是整個Pop大卡片樣式

  • 鉤子(填充觸點的動態資料)。對應紅包例子,鉤子就負責填充pop卡片中的紅包金額,和標題文案

其中,鉤子的定位是負責對接二三方的介面與服務,為素材投放提供動態資料。

現狀

  • 因為二三方服務介面格式都不同,做統一和抽象很困難。早期Luxury為了支撐業務快速上線,二三方服務接入都是用hard code的形式,case by case的解決。這種方式帶來的影響有:不僅不能支撐快速迭代的業務需求,並且已有服務也不能沉澱提供給其他策略複用。

  • 鉤子到觸點的對映需要運營同學手動填寫解析表示式,如下圖所示。需要運營同學理解後端的返回資料格式,配置成本高且容易配錯。

目標

  • 具備易用性:對運營來說不感知鉤子的底層差異,能夠快速完成鉤子到觸點的資料對映

  • 可擴充套件:能夠覆蓋大部分的業務接入場景

  • 可複用:能夠對鉤子進行一定程度抽象,避免每次業務接入都要開發新勾子

  • 可沉澱:鉤子沉澱為可複用資產,逐漸拓寬平臺能力邊界

技術方案

鉤子模組的總體思路是採用介面卡模式,抽象出鉤子介面層,二三方服務可以通過實現介面來接入Luxury平臺。

介面卡模式是可擴充套件性的經典解決方案。例如:

  • 雲原生監控系統Prometheus的exporter用來解決各種異構資料來源metrics上報的問題

  • 服務網格ServiceMesh的Mixer用來解決每次http呼叫時注入額外的業務邏輯

介面卡模式做統一抽象的解決方案一般是對出入參做資料對映,適配到統一的介面模型,對上層遮蔽底層實現差異。

鉤子模組架構圖:

鉤子模組鏈路圖:

入參構造

  • 問題:鉤子是策略鏈路上獨立可複用的模組,因此鉤子不與某個具體策略耦合,也不與某個具體的客戶端呼叫耦合。這意味著客戶端的每次呼叫不知道要傳哪些引數給鉤子。

  • 解決方案:該類問題的常規解決思路是把入參的結構資訊傳遞到呼叫方,讓呼叫方感知每次呼叫的入參格式。但這意味著需要拆分成兩次呼叫,一次獲取入參結構,一次發起呼叫。但在我們在橫向分析了自己的業務場景,發現策略的入參往往是精簡,有限,可列舉的,比如使用者id、商品id、頁面id等等。因此我們轉變方案,通過每次呼叫時客戶端傳遞儘可能全的上下文,而鉤子從呼叫上下文裡取需要的欄位。通過冗餘的引數換取更簡單的設計,更少的呼叫次數。

靜態配置視覺化

  • 問題:運營需要在後臺配置鉤子的靜態配置,但問題是每個鉤子的靜態配置結構都不一樣,服務端的靜態配置結構需要透傳到後臺生成表單讓運營感知並且理解。傳統解決前後端引數傳遞的方式是前後端約定介面協議並且開發頁面表單,但這種方式對鉤子不太適用。每次變更都涉及到前後端聯調,釋出,不能滿足鉤子敏捷擴充套件的需求。

  • 解決方案:要解決表單多變且可持續拓展的問題,用schema動態生成表單是一個好的解決方案。該方案需要前後端約定schema協議,前端提供schema生成表單的引擎,後端提供類生成schema的工具。這樣後端的類能直接對映為前端的表單樣式,前端表單的一份配置就對應一個後端類例項。整個開發流程能大幅度縮短,支撐鉤子敏捷迭代。

一份生成的表單與對應的schema例子如下:

{
"type": "object",
"properties": {
"appId": {
"type": "integer",
"title": "app id",
"required": true
},
"callSource": {
"type": "string",
"default": "fleamarket",
"title": "call source",
"required": true
},
"tppParam": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string",
"title": "tpp入參",
"required": true
},
"value": {
"type": "string",
"default": "${input.}",
"description": "支援從input或config從動態獲取引數",
"title": "解析表示式",
"required": true
}
}
},
"description": "從input或config中根據mvel表示式獲取引數,最後組裝為kv結構的tppParam",
"title": "tpp請求param"
}
}
}

出參標準化

  • 問題:鉤子作為業務擴充套件模組,出參結構是不可窮舉的,難以約束和進一步抽象。但矛盾在於如果不做統一的抽象,就會把出參的複雜度暴露給外部,外部就需要感知鉤子各種case下的資料結構,如何對出參做統一與抽象,遮蔽出參的複雜性?

  • 分析:我們配置觸點與資料的對映時。觸點用一個一個佔位符挖出來動態資料的“坑”,“坑”是有限且是單一層級的,不會出現一個“坑”裡填了個大物件或者大列表的場景。

  • 解決方案:制定規範,要求將鉤子的出參對映為平鋪的結構,每份平鋪的出參都是一份字典(KVKV結構),每個KV語義清晰,運營同學配置策略時,從字典裡挑選需要的動態資料欄位填入觸點,“將有限的資料填入有限的坑”。平鋪經驗證是滿足擴充套件性需求的,就算是巢狀的List結構,例如K[0:[0,1], 1:[0,1]]也能平鋪為K_0_0,K_0_1,K_1_0,K_1_1這種結構。

一個通過字典配置觸點的例子如下:

資料對映可配置化

  • 問題:在做紅包權益鉤子的時候,我們發現紅包權益鉤子的可複用性不強,因為它僅是TPP演算法中臺上的一種業務場景,我們一旦需要接TPP演算法中臺的另一個演算法業務場景,就需要重新擴充套件實現一遍鉤子。

  • 分析:可複用性不強的原因在於對接中臺類鉤子時,中臺本身有很強的拓展能力,而我們的出入參的資料對映針對中臺上的一種業務場景寫死的,導致不能複用到中臺上的其他業務場景。因此,我們需要做到資料對映的可配置化。

  • 解決方案:當發現數據對映成為擴充套件性的瓶頸時,就要著手解決資料對映靈活性的問題。因此需要實現資料對映的可配置化,鉤子實現可配置的資料對映引擎,將入參出參的資料對映配置關係抽出放到靜態引數裡。這樣中臺鉤子就不與任意一個場景耦合,變更場景時只需要修改資料對映配置,不需要重新開發。當鉤子實現不與具體場景耦合時,也就實現了最大化靈活性。

效果

  • 運營同學可以獨立配置鉤子到觸點的對映關係,鉤子成為了可以靈活替換的資料字典,可以按需挑選自己需要的動態資料。

  • 沉澱了常見中臺鉤子,能夠覆蓋大部分場景需求,服務接入僅需0.5day配置成本。

  • 覆蓋促買入、促發布、促瀏覽、活動互動等多個業務場景。 

總結

擴充套件模組的設計都很“難”,難的原因在於為了保證可擴充套件性,在方案設計階段就不能預設接入服務的結構。接入服務可以是任意一個介面,任意一個服務。對於這樣沒有邊界的服務要做統一和抽象是很困難的。

關鍵的地方在於統一介面層的定義,也是“規範”和“協議“定義。需要我們從業務特點出發,在保證可擴充套件性的前提下,保證介面層的語義清晰,在可擴充套件性和易用性之間找到最佳的平衡點。