关于美团:美团开放平台SDK自动生成技术与实践

52次阅读

共计 10746 个字符,预计需要花费 27 分钟才能阅读完成。

美团开放平台为整个美团提供了 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 申请受权。

正文完
 0