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

美團技術團隊發表於2023-01-08
美團開放平臺為整個美團提供了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年貨】等關鍵詞,可檢視美團技術團隊歷年技術文章合集。

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

相關文章