美團開放平臺SDK自動生成技術與實踐

美團開放平臺爲整個美團提供了20+業務場景的開放API,爲了使開發者能夠快速且安全的接入美團開放平臺,美團開放平臺提供了多種語言的SDK來提高開發者的接入效率。本文介紹了美團開放平臺如何自動生成SDK代碼的相關技術實現方案,希望對大家能夠有所幫助或者啓發。

1. 引言

美團開放平臺對外提供了外賣、團購、配送等20餘個業務場景的OpenAPI,供第三方開發者搭建應用時使用,是美團系統與外部系統通訊的最重要平臺。本文主要講述開放平臺如何通過技術手段自動生成支持接口參數富模型和多種編程語言的SDK,以提高開發者對接開放平臺API的效率。

美團開放平臺架構

1.1 背景

美團開放平臺將美團各類業務提供的擴展服務封裝成一系列應用程序編程接口(API)對外開放,供第三方開發者使用。開發者可通過調用開放平臺提供的OpenAPI獲取數據和能力,以實現自身系統與美團系統協同工作的業務邏輯。以外賣業務場景爲例,開發者可以在自己爲外賣商戶開發的應用中通過調用美團開放平臺提供的API,提供外賣訂單查詢、接單、訂單管理等一系列功能。如下圖所示:

開放平臺爲開發者提供的OpenAPI以HTTP接口的形式提供。以平臺提供的訂單查詢接口爲例,對應的HTTP請求如下所示:

POST https://api-open-cater.meituan.com/api/order/queryById
Content-Type: application/x-www-form-urlencoded;charset=utf-8

appAuthToken=eeee860a3d2a8b73cfb6604b136d6734283510c4e92282&
charset=utf-8&
developerId=106158&
sign=4656285a4c2493e279d929b8b9f4e29310da8b2b&
timestamp=1618543567&
biz={"orderId": "10046789912119"}

Response:{
  "orderId":"10046789912119",
  "payAmount":"45.67",
  "status":7,
  ......,
  "products":[{"pid":"8213","num":2,...,"price":"3.67"}{"pid":"6556","num":1,...,"price":"11.99"}]
}

由上述示例可以看出,美團開放平臺提供給開發者的接口契約較爲複雜,其中包含了業務規則複雜及安全性要求高等原因。若開發者需要直接從0到1編碼對接平臺提供的HTTP API,需要關注通信協議、接口契約規範、認證標識傳遞和安全簽名等細節,成本較高。隨着業務的發展,平臺支持的OpenAPI數量在近兩年增長約一倍,達到近1000個,平臺運營和研發人員需要投入越來越多的精力去幫助開發者解決接口對接過程中的疑難問題。因此,提供SDK以幫助開發者提高開發對接效率,變得十分有必要。

1.2 SDK目標概述

SDK,英文名稱爲 Software Development Kit,即軟件開發工具包,廣義上指輔助開發某一類軟件的相關工具、文檔和範例的集合。在開放平臺的場景,我們爲開發者提供的SDK應能爲其屏蔽調用OpenAPI的通信協議、參數傳遞規範、接口基礎契約(如時間戳、安全簽名)等細節,以降低其對接平臺API所需的開發成本。具備基本功能的開放平臺SDK的架構和功能模塊如下所示:

從使用SDK的開發者角度來看,基於SDK封裝的基礎功能來編寫調用開放平臺接口的代碼,大致邏輯如下所示:

MeituanClient client = DefaultMeituanClient.builder(developerId, signKey).build();
//設置請求參數
MeituanRequest request = new MeituanRequest("/api/order/queryById");
request.setParam("orderId","10046789912119");
MeituanResponse response = client.invokeApi(req);
if(response.isSuccess()) {
  long price = (long)response.getField("price");
  String phone = response.getField("customerPhone");
  int orderStatus = (int)response.getField("status");
  //完成業務邏輯
} else {
  log.warn("query order failed with response={}", response);
  //處理接口調用失敗的邏輯
}

從上述代碼可以看出,提供基礎功能的SDK已經能夠爲使用者提供較大的便利。相比從零開始編碼對接OpenAPI,使用SDK可以幫助開發者省去處理通信協議、公共參數放置、安全簽名計算和返回狀態碼解析的工作量。但開發者在編寫代碼設置API的業務參數字段的環節,仍需對照API文檔逐個手工填充字段名並按字段類型賦值,並且在獲取API返回的業務字段時也需自主填充字段名並解析數據類型,存在較大的不便且易出錯。

爲解決此問題,我們需要在SDK的能力上更進一步提供對參數富模型的支持,即爲每個API提供模型化封裝的請求參數和返回參數結構,讓使用SDK的開發者可以更加專注於業務邏輯的開發。

在SDK加入參數富模型的支持後,從使用者的角度來看,需要編寫的代碼如下所示:

MeituanClient client = DefaultMeituanClient.builder(developerId, signKey).build();
//設置請求參數
QueryOrderRequest request = new QueryOrderRequest();
request.setOrderId("10046789912119");
//調用接口
MeituanResponse<QueryOrderResponse> response = client.invokeApi(req);
//處理接口返回
if(response.isSuccess()) {
  QueryOrderResponse orderResponse = response.getData();
  long price = orderResponse.getPrice();
  String phone = orderResponse.getCustomerPhone();
  int orderStatus = orderResponse.getStatus();
  log.info("query order finish, price={}, orderStatus={}", price, phone, orderStatus);
} else {
  log.warn("query order failed with response={}", response);
  //處理接口調用失敗的邏輯
}

可以看出,參數富模型功能可以進一步減少開發者使用SDK的複雜度。以Java語言版本爲例,QueryOrderRequest和QueryOrderResponse兩個富模型類中封裝了API的請求參數和返回參數的所有字段名、字段類型和字段校驗規則等信息,開發者可簡單使用字段的getter和setter方法完成對字段的賦值和取值操作,大幅降低了理解成本和出錯可能。

雖然在SDK中支持參數富模型功能,可以有效提高使用者的效率,但也會帶來SDK的開發和維護成本增加。如果採用純人工的方式去開發維護SDK中支持的所有API的參數模型代碼,需要投入的開發維護成本與SDK支持的編程語言數量和API數量呈正相關性,其成本公式爲:

從上述公式可以看出,當SDK所需支持的API數量和編程語言數量達到一定數量時,通過純人工編碼去開發和維護SDK的成本會非常高。需要通過技術手段自動生成和測試SDK中的絕大部分代碼,以達到在成本可控的前提下,爲開發者提供支持多種編程語言版本的富模型SDK的目標。

2. SDK自動生成技術詳解

2.1 整體設計

要爲開發者提供一個支持參數富模型功能的OpenAPI SDK,我們需要實現以下主要功能:

  1. 通信協議封裝:讓開發者無需關注調用API的通信協議和通信邏輯。
  2. 接口基礎契約封裝:讓開發者無需關注調用API的參數傳遞格式、時間戳、安全簽名、返回Code碼處理等細節。
  3. 請求參數模型封裝:讓開發者便捷地設置API請求參數。
  4. 返回參數模型封裝:讓開發者便捷地使用API返回的數據。

其中,通信協議封裝和接口基礎契約封裝是一次性工作,並且其邏輯是相對穩定的。對於SDK所需支持的每一種編程語言,只需投入有限的成本開發一次對應代碼邏輯,即可支撐SDK的整個生命週期。而要爲平臺開放的1000餘個API提供支持多種編程語言的參數富模型功能,靠人工編寫和維護代碼是極其低效的,我們考慮通過代碼自動生成技術,對SDK中的參數富模型代碼進行自動化生成。

更進一步,在實現了參數富模型代碼自動生成後,我們可以通過持續集成(Continious Integration)和持續發佈(Continuous Delivery)技術,將SDK的生成、測試和發佈流程也儘可能地做到自動化。整體的SDK自動生成流程設計如下圖所示:

實現了以上流程後,即可做到在開放平臺的任意API的參數模型發生變化時,由系統自動生成和發佈最新版本的SDK供開發者使用。我們將在下文詳述如何通過代碼自動生成、持續集成和持續發佈等技術手段實現上述流程。

2.2 自動生成參數模型代碼

我們最終的目標是爲開放平臺的每個OpenAPI,自動生成供SDK使用的請求參數模型代碼(Request類)、返回參數模型代碼(Response類)和調用示例代碼(Example),並且代碼自動生成機制要支持SDK適配的多種編程語言。以Java和C#編程語言爲例,我們要生成的目標代碼如下圖所示:

從上面的示例中可以看出,在請求參數模型(Request類)中需要生成Request Path、鑑權配置、字段強類型定義、字段取值、賦值及校驗邏輯等代碼。在返回參數模型(Response類)中,需要生成接口返回的各個數據字段的強類型定義、取值邏輯及校驗規則。調用示例代碼則需要包含請求參數賦值、發起接口調用和處理接口返回數據等相關邏輯。

要達成上述目標,首先需要考慮的是代碼自動生成技術的選型,目前業界主流的代碼生成技術分爲以下幾類:

  1. 基於模版編排生成代碼:最原始最簡單也是目前應用最廣泛的一種代碼生成方式。包括後端MVC框架的Controller、Service、DAO層模式化代碼一鍵生成,還有前端Vue CLI 和Create-React-App兩款腳手架的代碼生成,都屬於此類。
  2. 基於可視化UI生成代碼:目前市場上運用得很廣的一門技術,也被稱爲代碼可視化生成工具。從Eclipse的Web可視化編輯器,到.NET Framework提供的MVC,及Winform界面及控件代碼可視化拖拽生成,到汽車行業廣泛使用的可視化原型搭建工具(自動生成C代碼)都屬於此類。在近幾年比較火的低代碼平臺(如aPaaS)中,通過可視化UI生成代碼的技術也被大量使用。
  3. 基於代碼語料生成代碼:基於代碼語料生產代碼的前提是要有足夠的語料,例如僞代碼/中間語言/描述性代碼模板,再基於一套生成規則去生成目標代碼。常見的落地場景包括RPC框架中基於IDL(Interface description language,接口描述語言)自動生成多種編程語言的RPC Client和Service代碼,以及IDE插件中的代碼自動生成功能(例如Eclipse的telosys插件可通過DSL生成多種語言代碼)。
  4. 基於人工智能技術生成代碼:屬於比較前沿的技術範疇,多和AI領域的圖像識別和機器學習技術結合。現有的一些典型案例包括:微軟開發的可將手繪圖轉化HTML代碼的智能化代碼生成工具sketch2code,基於AI技術自動生成UI邏輯的teleporthq

考慮到開放平臺SDK中,需要自動生成的OpenAPI參數富模型代碼和調用示例代碼均具備相對較強的規則性和模式性,我們選擇基於代碼語料自動生成代碼的技術路線。

基於代碼語料自動生成代碼需要“語料”+“規則”兩個核心元素,我們可以通過解析API元數據並結合領域專用語言(DSL)作爲語料模板,生成代碼語料,再基於語料特性爲不同的編程語言定製代碼生成規則,最終將“語料”+“規則”輸入代碼生成器以完成目標代碼的生成。整體流程如下圖所示:

在上述流程中,首先關注作爲代碼語料生成數據源的API元數據,其來源於開放平臺實現的零編碼API網關底層維護的基礎配置。開放平臺網關基於API元數據配置化的技術,可做到零編碼將業務服務的RPC接口轉化爲HTTP協議的API進行開放。其基本運行結構如下圖所示:

作爲驅動開放平臺網關運行的核心數據,API元數據中包含了HTTP Method、URL、請求參數、返回參數等信息。在參數信息中,又以樹形結構記錄了每個參數字段的字段名、字段類型、字段描述、校驗規則和示例值。我們以“按訂單id查詢訂單詳情”的API爲例,其元數據中和SDK生成相關的數據如下所示:

APIGroup:waimai
APISubGroup:order
APIName: order_query_by_id
HTTP METHOD: POST
HTTP PATH: /api/order/queryById
Description: 按訂單id查詢訂單詳情
Request
  |- orderId LONG NOT_NULL 要查詢的訂單的id example:1000224201796844308
Response
  |- orderId  LONG NOT_NULL 訂單id  example:1000224201796844308
  |- price  LONG NOT_NULL 訂單金額(單位爲人民幣“分”) example:3308
  |- phone  STRING  顧客聯繫電話   example:"13000000002"
  |- products  ARRAY<Product>  訂單商品列表
     |- pid  LONG  商品id   example:"13000000002"
     |- name  String  商品名  example:"珍珠奶茶"
     |- num  INTEGER  商品數量  example:1
     |- price  LONG  商品單價   example:1199
     |- properties  ARRAY<Property>  商品屬性列表
        |- name STRING 商品屬性名  example:"甜度"
        |- value STRING 商品屬性值  example:"七分糖"
     |- remark  STRING  商品備註  example:"請做常溫的"
  |- status  INTEGER  訂單狀態  example:7

以上信息足以支撐我們爲SDK生成參數富模型和調用示例代碼。下一步我們需要開始處理代碼語料,併爲最終的代碼自動化生成做好準備。不同編程語言所需的代碼語料有所差異,但同一類編程語言(如Java和C#都是面向對象的編程語言)大致相同。

以生成Java SDK中的參數富模型代碼爲例,需要用到的代碼語料包含兩部分。第一部分爲類的基本信息,由元數據解析器在解析API的元數據時生成,其包含的內容和具體生成方式如下表所示:

第二部分爲語料模板,我們以DSL(Domain Specific Language)作爲中間語言加以描述,如下所示:

<@class className=className metaInfo=javaApiMeta baseClass=baseClass interfaces=interfaces classDesc=classDesc package=packageName importPackages=importPackages>
    <#-- 靜態字段   -->
    <#if staticFields?? && (staticFields?size > 0) >
        <#list staticFields as param>
            <@staticField param=param/>
        </#list>
    </#if>
    <#-- 字段   -->
    <#if privateFields?? && (privateFields?size > 0) >
        <#list privateFields as param>
            <@field param=param/>
        </#list>
    </#if>
   <#-- Getter/Setter -->
    <#if privateFields?? && (privateFields?size > 0) >
        <#list privateFields as param>
            <@getterMethod param=param/>
            <@setterMethod param=param/>
        </#list>
    </#if>
    
    <#-- 靜態字段Getter -->
    <#if staticFields?? && (staticFields?size > 0) >
        <#list staticFields as param>
            <@getterMethod param=param/>
        </#list>
    </#if>

    <#if javaApiMeta?has_content>
        <@deserializeResponse metaInfo=javaApiMeta/>
        <@serializeToJson metaInfo=javaApiMeta/>
    </#if>

    <#-- toString方法 -->
    <#if privateFields?? && (privateFields?size > 0) >
        <@toString className=className params=privateFields/>
    </#if>
</@class>

有了上述的代碼語料,我們即可通過語言轉換引擎生成Java代碼。我們將解析好的API元數據作爲輸入,執行基於DSL的語言轉換引擎。語言轉換引擎通過執行宏命令將要生成的代碼類的基本信息在DSL語料模板中進行填充,最終得到Java編程語言的目標類及其附屬類的代碼。以生成Response類代碼爲例,代碼生成的具體執行過程如下圖所示:

Request和Response類中其餘的getter方法、setter方法、類註解等元素的生成原理和步驟均和以上相同,此處不再贅述。在DSL語料模板中所有的元素處理完成後,我們即可得到供Java編程語言使用的請求參數類和返回參數類的完整代碼。

對於其他的編程語言(例如Python),我們使用的API元數據和元數據解析邏輯和Java是一致的,不同點在於DSL語料模板和語言轉換引擎。當需要對SDK新增一種編程語言的支持時,我們只需要對目標語言建立DSL語料模板並提供相應的轉換邏輯,即可支持該語言的請求參數類和返回參數類的代碼自動生成。

2.3 自動生成API調用示例代碼

通過同樣的技術手段,我們還可以自動生成每個OpenAPI的調用示例代碼,並將示例代碼展示接口文檔中供開發者參考。

調用示例代碼的生成的邏輯相對參數模型代碼更加簡單。我們使用API元數據中的類名和字段信息(元數據中也包含了每個字段的examle值,可用於在代碼示例中生成字段賦值的邏輯)填入代碼語料中,再執行語言轉換引擎生成目標代碼即可。以Java編程語言爲例,用於生成API調用示例代碼的DSL語料模板如下所示:

<#setting number_format="computer">
MeituanClient meituanClient = DefaultMeituanClient.builder(10000L, "xxxxx").build();

<#assign reqVarName = className?uncap_first/>
${className} ${reqVarName} = new ${className}();

<#if privateFields?? && (privateFields?size > 0)>
<#list privateFields as field>
${reqVarName}.set${field.fieldName?cap_first}(${field.exampleValue!""});
</#list>
</#if>

<#if javaApiMeta.needAuth>
String appAuthToken = "xxxx";
MeituanResponse<${javaApiMeta.responseClass}> response = meituanClient.invokeApi(request, appAuthToken);
<#else >
MeituanResponse<${javaApiMeta.responseClass}> response = meituanClient.invokeApi(request);
</#if>

if (response.isSuccess()) {
<#if javaApiMeta.responseClass == "Void">
    System.out.println("調用成功");
<#else>
    ${javaApiMeta.responseClass} resp = response.getData();
    System.out.println(resp);
</#if>
} else {
    System.out.println("調用失敗");
}

在使用API元數據和代碼語料模板執行基於DSL的語言轉換引擎後,生成的API調用示例代碼如下所示:

MeituanClient client = DefaultMeituanClient.builder(developerId, signKey).build();
//設置請求參數
OrderQueryByIdRequest request = new OrderQueryByIdRequest();
request.setOrderId(1000224201796844308L);
//調用接口
MeituanResponse<OrderQueryByIdResponse> response = client.invokeApi(req);
//處理接口返回
if(response.isSuccess()) {
  OrderQueryByIdResponse orderResponse = response.getData();
  System.out.println(orderResponse);
} else {
  System.out.println("調用失敗");
}

可以看出,我們生成的API調用示例代碼可以爲開發者呈現出每個請求參數賦值的示例邏輯,可有效降低開發者在對接API時的理解成本。後續我們可以進一步優化DSL語料模板,在示例代碼中增加對返回數據結構中各個字段的取值邏輯示範,以進一步降低開發者在處理API返回數據時的理解和開發成本。

2.4 持續集成和持續發佈

搞定參數富模型代碼和調用示例代碼的自動生成後,下一步是通過持續集成和持續發佈技術,確保開發者在任何時刻均能獲取到最新版本的SDK。傳統由人工編譯、測試和上傳發布SDK的模式,開發者得到SDK版本更新的週期短則數週,長則數月。我們的目標是將這個週期縮短到分鐘級別:當SDK的基礎邏輯和API參數模型有任何變更發生時,通過持續集成和持續發佈的能力,在數分鐘內將包含此變更的新版本SDK發佈給開發者使用。

我們基於美團自研的流水線引擎來驅動SDK的持續集成和持續發佈。流水線的執行可以看作是對生成SDK的“原材料”一步步加工,最終交付到線上的過程。先通過下圖瞭解整體流程:

首先我們監聽可能導致SDK需要發佈的變更,包括通過Binlog機制監聽API元數據的變更,以及通過Git Hook機制監聽SDK基礎邏輯代碼倉庫Master分支的變更。一旦監聽到有變更產生,通過觸發器去觸發SDK持續集成和發佈流水線的運作。

流水線開始運作後,首先執行SDK構建組件,SDK構建組件會併發執行兩個操作:

  1. 獲取SDK基礎邏輯代碼(人工編寫)並完成靜態代碼檢查;
  2. 拉取API元數據並自動生成參數富模型代碼。

以上兩個操作完成後,執行代碼合併和代碼編譯,將結果提交到流水線執行下一個步驟。接下來由自動化測試組件完成對SDK的單元測試和端到端自動化測試,通過後提交到流水線執行下一個步驟。最後由自動發佈組件完成SDK的打包、上傳、下載鏈接生成和版本信息生成等一系列操作,並最終將最新版本SDK發佈到官網供開發者下載。

3. 結語

通過上述能力的建設,我們打通了SDK自動生成的整個環節,以自動化的方式完成代碼生成、構建、測試、集成、發佈等一系列行爲,最終實現了在低人力投入的前提下持續向開發者交付最新版本SDK的目標。

通過最近半年數據的對比,我們可以看出開發者使用SDK後在接口對接環節遇到的疑難問題明顯減少。基本達到了我們最初提高開發者接入效率,降低平臺研發和運營處理工單成本的目標。

後續,我們將會計劃繼續完善SDK的代碼自動生成邏輯,併爲SDK添加更多編程語言的支持,爲接入美團開放平臺的開發者提供更好的體驗。

4. 寫在後面

不久前,美團獨立申報的智慧生活國家新一代人工智能開放創新平臺正式獲得中華人民共和國科學技術部(以下簡稱“科技部”)批覆。這是美團第一個國家級科研平臺

國家新一代人工智能開放創新平臺被稱爲“人工智能國家隊”,是聚焦人工智能重點細分領域,充分發揮行業領軍企業的引領示範作用,有效整合技術資源、產業鏈資源和金融資源,持續輸出人工智能核心研發能力和服務能力的重要創新載體。此前,已有百度、阿里、騰訊等15家公司先後獲批建設。本次美團成功申報,標誌着美團的科研創新能力獲得了國家層面認可,達到“國家隊水平”。

5. 本文作者

飛宏、照東、宇豪、王鴻等,均來自美團到店事業羣/餐飲SaaS事業部。

閱讀美團技術團隊更多技術文章合集

前端 | 算法 | 後端 | 數據 | 安全 | 運維 | iOS | Android | 測試

| 在公衆號菜單欄對話框回覆【2021年貨】、【2020年貨】、【2019年貨】、【2018年貨】、【2017年貨】等關鍵詞,可查看美團技術團隊歷年技術文章合集。

| 本文系美團技術團隊出品,著作權歸屬美團。歡迎出於分享和交流等非商業目的轉載或使用本文內容,敬請註明“內容轉載自美團技術團隊”。本文未經許可,不得進行商業性轉載或者使用。任何商用行爲,請發送郵件至[email protected]申請授權。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章