1. 引言
美团开放平台对外提供了 20 余个业务场景,包括外卖、团购、配送等,供第三方开发者搭建应用时使用,它是美团系统与外部系统通讯的极为重要的平台。本文主要讲述开放平台怎样通过技术手段自动生成支持接口参数富模型以及多种编程语言的 SDK,从而提高开发者对接开放平台 API 的效率。
1.1 背景
美团开放平台把美团各类业务所提供的扩展服务进行封装,封装成一系列的应用程序编程接口(API),然后将这些接口对外开放,让第三方开发者能够使用。开发者能够通过调用开放平台所提供的获取数据的功能以及相关能力,从而实现自身系统与美团系统协同工作的业务逻辑。以外卖业务场景作为例子,开发者能够在其为外卖商户所开发的应用里,通过调用美团开放平台给出的 API,来提供外卖订单的查询功能、接单功能以及订单管理等一系列的功能。如下图所呈现的那样:
开放平台以 HTTP 接口的形式为开发者提供服务。以平台提供的订单查询接口为例,其对应的 HTTP 请求情况如下:
POST 到 https://api-open-cater.meituan.com/api/order/queryById
内容类型为 application/x-www-form-urlencoded 且字符编码为 utf-8 。
appAuthToken 等于 eeee860a3d2a8b73cfb6604b136d6734283510c4e92282 且后面跟一个与号(&)
charset=utf-8&
developerId=106158&
sign 的值为 4656285a4c2493e279d929b8b9f4e29310da8b2b 。
timestamp=1618543567&
biz 包含的信息为 {"orderId": "10046789912119"} 。其中,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"。
}
从上述示例能够得知,美团开放平台供给开发者的接口契约是比较复杂的。这种复杂性体现在包含了业务规则较为复杂以及安全性要求较高等方面。倘若开发者想要直接从无到有地进行编码,以对接平台所提供的 HTTP API,那就需要留意通信协议、接口契约规范、认证标识传递以及安全签名等这些细节,并且这样做的成本是比较高的。业务发展后,近两年平台支持的数量增长了约一倍,已达到近 1000 个。平台的运营和研发人员需投入越来越多精力,去帮助开发者解决接口对接过程中的疑难问题。所以,提供 SDK 来帮助开发者提高开发对接效率,是很有必要的。
1.2 SDK目标概述
SDK,其英文名称为 Kit,也就是软件开发工具包。从广义上来说,它指的是辅助开发某一类软件的相关工具、文档以及范例的集合。在开放平台的这种场景之下,我们为开发者所提供的 SDK,应当能够将调用的通信协议、参数传递规范、接口基础契约(像时间戳、安全签名等)等这些细节给屏蔽掉,以此来降低开发者对接平台 API 所需要的开发成本。具备基本功能的开放平台SDK的架构和功能模块如下所示:
从开发者使用 SDK 的角度而言,依据 SDK 所封装的基础功能去编写调用开放平台接口的代码,其大致逻辑呈现如下:首先,开发者利用 SDK 封装的基础功能;接着,基于这些基础功能来编写调用开放平台接口的代码;整体的大致逻辑就是这样。
创建 MeituanClient 实例,通过 DefaultMeituanClient 的 builder 方法,传入 developerId 和 signKey 进行构建,最终得到 MeituanClient client 。
//设置请求参数
创建了一个 MeituanRequest 对象,其请求的路径为 /api/order/queryById 。
request 把 "orderId" 设定成 "10046789912119" ;
MeituanResponse 这个 response 是由 client 调用 api 所产生的,具体的调用过程是 client.invokeApi(req) 。
if(response.isSuccess()) {
price 等于 response 获取字段 "price" 的结果并转换为 long 类型。
从 response 中获取名为“customerPhone”的字段,其值赋给字符串 phone 。
获取响应中的“status”字段,将其转换为整数类型并赋值给 orderStatus 变量。
//完成业务逻辑
} else {
log 发出警告,内容为“查询订单失败,响应为={}”,其中响应为 response 。
//处理接口调用失败的逻辑
}
从上述代码能够看出,提供基础功能的 SDK 具备为使用者提供便利的能力。与从零开始编码对接相比,使用 SDK 能帮助开发者省去处理诸多方面的工作量,比如通信协议的处理、公共参数的放置、安全签名的计算以及返回状态码的解析。开发者在编写代码设置 API 的业务参数字段时,需要对照 API 文档逐个进行手工填充字段名的操作,并且要按照字段类型进行赋值。同时,在获取 API 返回的业务字段时,也需要自主填充字段名并对数据类型进行解析。这样的操作存在较大的不便,并且容易出错。
为解决此问题,我们需在 SDK 的能力方面更进一步,为其提供对参数富模型的支持。也就是要为每个 API 提供模型化封装的请求参数和返回参数结构,这样使用 SDK 的开发者就能更专注于业务逻辑的开发。
SDK加入参数富模型的支持后,从使用者角度而言,所需编写的代码呈现如下:
MeituanClient client = DefaultMeituanClient.builder(developerId, signKey).build();
//设置请求参数
创建了一个 QueryOrderRequest 的请求对象,即 request 。
请求把自身的订单 ID 设定为 "10046789912119";
//调用接口
MeituanResponse客户端调用了 API,调用的请求是 req,调用的结果是 response。
//处理接口返回
if(response.isSuccess()) {
QueryOrderResponse 的 orderResponse 是 response 中的数据。
获取到的订单响应中的价格被赋值给了 long 类型的变量 longPrice ,即 longPrice = orderResponse.getPrice() ;
获取到的订单响应中的客户电话被存储在字符串 phone 中。
获取订单响应中的状态值并赋值给订单状态变量,即订单状态等于订单响应的状态。
log 记录了查询订单完成的信息,其中价格为{},订单状态为{},这里的价格是 price,订单状态是 orderStatus,同时还记录了电话 phone 相关信息。
} else {
log.warn("query order failed with response={}", response);
//处理接口调用失败的逻辑
}
可以看出,参数富模型功能能够进一步降低开发者使用 SDK 的复杂度。比如以 Java 语言版本来说,两个富模型类封装了 API 的请求参数和返回参数的所有相关信息,包括字段名、字段类型以及字段校验规则等。这样一来,开发者可以简便地利用字段的相关操作来完成对字段的赋值和取值,从而大大降低了理解成本以及出错的可能性。
SDK 中支持参数富模型功能,这能有效提高使用者的效率,不过也会使 SDK 的开发和维护成本增加。若用纯人工方式开发维护 SDK 中支持的所有 API 的参数模型代码,那么投入的开发维护成本与 SDK 支持的编程语言数量和 API 数量呈正相关,其成本公式为:
从上述公式能看出,若 SDK 所需支持的 API 数量以及编程语言数量达到一定程度,那么仅靠纯人工编码来开发和维护 SDK 的成本会极高。需要借助技术手段,自动生成并测试 SDK 中的绝大部分代码,这样才能在成本可控制的情况下,实现为开发者提供支持多种编程语言版本的富模型 SDK 的目标。
2. SDK自动生成技术详解2.1 整体设计
为开发者提供一个 SDK,这个 SDK 要支持参数富模型功能,我们需要实现以下这些主要功能:
对返回参数进行模型封装,可让开发者便捷地使用 API 返回的数据。
其中,通信协议的封装以及接口基础契约的封装属于一次性的工作,并且其逻辑处于相对稳定的状态。对于 SDK 所需要支持的每一种编程语言而言,只需投入有限的成本去开发一次与之对应的代码逻辑,就能够支撑 SDK 的整个生命周期。要为平台开放的 1000 余个 API 提供支持多种编程语言的参数富模型功能,若仅靠人工编写和维护代码,效率会极其低下。因此,我们考虑运用代码自动生成技术,来对 SDK 中的参数富模型代码进行自动化生成。
在实现了参数富模型代码自动生成之后,我们能够借助持续集成( )以及持续发布( )技术,把 SDK 的生成、测试以及发布流程都尽量实现自动化。整体的 SDK 自动生成流程设计情况如下图所示:
实现了上述流程后,当开放平台的任意 API 的参数模型发生变化时,系统能够自动生成并发布最新版本的 SDK 供开发者使用。我们将在下文详细说明如何通过代码自动生成、持续集成以及持续发布等技术手段来达成上述流程。
2.2 自动生成参数模型代码
我们的最终目标是为开放平台的每一个,生成供 SDK 使用的请求参数模型代码(类)、返回参数模型代码(类)以及调用示例代码()。同时,代码自动生成机制要能够支持 SDK 适配的多种编程语言。比如以 Java 和 C#编程语言为例,我们要生成的目标代码就如同下面的图所示:
从上面示例可知,请求参数模型(类)需生成 Path 等代码,还需生成鉴权配置等代码,以及字段强类型定义等代码,还有字段取值等逻辑及赋值和校验逻辑等代码。在返回参数模型(类)中,要生成接口返回的各数据字段的强类型定义,以及取值逻辑和校验规则。调用示例代码需包含请求参数赋值等相关逻辑,要发起接口调用,还要处理接口返回数据等相关逻辑。
要达成上述目标,首先需考虑的是代码自动生成技术的选型。目前,业界主流的代码生成技术可分为以下几类:
可视化 UI 可生成代码,这是目前市场上运用很广的一门技术,也被称作代码可视化生成工具。Web 有可视化编辑器,.NET 提供了 MVC,还有界面及控件代码能通过可视化拖拽生成,汽车行业广泛使用的可视化原型搭建工具(可自动生成 C 代码)都属于这类。近几年比较火的低代码平台(如某些平台)中,通过可视化 UI 生成代码的技术被大量运用。基于代码语料来生成代码,其前提是必须拥有足够的语料。这些语料可以是伪代码、中间语言或者描述性的代码模板等。然后,依据一套生成规则去生成目标代码。常见的落地场景包含 RPC 框架中依据 IDL(接口描述语言)自动生成多种编程语言的 RPC 以及代码,还有 IDE 插件里的代码自动生成功能,比如有的插件能够通过 DSL 生成多种语言的代码。基于人工智能技术来生成代码,这属于较为前沿的技术范畴,并且多与 AI 领域的图像识别和机器学习技术相融合。现有的一些典型案例有:微软开发了一种智能化代码生成工具,它能够将手绘图转化为 HTML 代码;还有基于 AI 技术自动生成 UI 逻辑的工具。
开放平台 SDK 中,自动生成的参数富模型代码以及调用示例代码都具有相对较强的规则性和模式性。基于此,我们选择了基于代码语料自动生成代码的技术路线。
基于代码语料自动生成代码需要两个核心元素,分别是“语料”和“规则”。我们能够解析 API 元数据,并将其与领域专用语言(DSL)结合作为语料模板,从而生成代码语料。接着,依据语料特性为不同的编程语言定制代码生成规则。最后,把“语料”和“规则”输入到代码生成器中,以完成目标代码的生成。整体流程如下图所示:
在上述流程里,首先要关注 API 元数据,它是作为代码语料生成的数据源。这种 API 元数据来源于开放平台实现的零编码 API 网关底层所维护的基础配置。开放平台网关凭借 API 元数据配置化的技术,能够做到零编码,将业务服务的 RPC 接口转化为 HTTP 协议的 API 来进行开放。其基本运行结构如下图所示:
核心数据驱动着开放平台网关的运行,API 元数据包含了 HTTP、URL、请求参数、返回参数等信息。在这些参数信息里,是以树形结构记录了每个参数字段的相关内容,如字段名、字段类型、字段描述、校验规则和示例值。以“按订单 id 查询订单详情”的 API 为例,其元数据中与 SDK 生成相关的数据如下所示:
APIGroup:waimai
APISubGroup:order
APIName: order_query_by_id
HTTP METHOD: POST
HTTP 的路径为:/api/order/queryById
Description: 按订单id查询订单详情
Request
要查询的订单的 id 为 orderId 且为 LONG 类型且不为 NULL,例如:1000224201796844308
Response
订单 id 为 LONG 类型且不为 NULL,例如:1000224201796844308
订单金额(单位为人民币“分”)为 LONG 类型且不能为空,例如 3308
顾客联系电话为 phone STRING ,例如:"13000000002"
|- products ARRAY 订单商品列表
商品 id 为 "13000000002",其对应的 pid 为 LONG
商品名是“珍珠奶茶”。
商品数量为整数,例如 1 。
商品单价为 1199 ,价格较长。
|- properties ARRAY 商品属性列表
商品属性名被表示为 |- name STRING,例如“甜度”。
商品属性值为“七分糖”。
商品备注的示例为:请做常温的。
订单状态的 status 为整数,例如 7 。
以上信息能够支撑我们为 SDK 生成参数富模型以及调用示例代码。接下来我们得开始对代码语料进行处理,同时为最终的代码自动化生成做好准备。不同的编程语言所需要的代码语料存在差异,然而同一类编程语言(例如 Java 和 C#都属于面向对象的编程语言)大致是相同的。
以生成 Java SDK 中的参数富模型代码作为例子,所需用到的代码语料包含两个部分。一部分是类的基本信息,这部分信息是由元数据解析器在解析 API 的元数据时生成的,它所包含的内容以及具体的生成方式如下表所示:
第二部分属于语料模板,我们把 DSL( )当作中间语言来进行描述,具体如下:
<@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 编程语言的目标类以及其附属类的代码。以生成类代码为例,代码生成的具体执行过程如下图所示:
类中除上述元素外,其余方法、注解等元素的生成原理和步骤与上述相同,此处不再详细说明。在 DSL 语料模板中完成所有元素处理后,就能得到供 Java 编程语言使用的请求参数类和返回参数类的完整代码。
对于其他编程语言(例如),我们使用的 API 元数据以及元数据解析逻辑与 Java 相同。不同之处在于 DSL 语料模板和语言转换引擎。若要为 SDK 新增一种编程语言的支持,只需为目标语言建立 DSL 语料模板并提供相应的转换逻辑,就能支持该语言的请求参数类和返回参数类的代码自动生成。
2.3 自动生成API调用示例代码
我们可以通过相同的技术手段,自动生成每个的调用示例代码,并且把这些示例代码展示在接口文档中,供开发者参考。
调用示例代码的生成逻辑比参数模型代码的生成逻辑更简单。我们将 API 元数据中的类名和字段信息(元数据中包含每个字段的值,可用于在代码示例中生成字段赋值逻辑)填入代码语料中,然后执行语言转换引擎来生成目标代码。以 Java 编程语言为例,用于生成 API 调用示例代码的 DSL 语料模板如下:
<#setting number_format="computer">
MeituanClient 这个对象,是通过 DefaultMeituanClient 的 builder 方法创建的,传入了 10000L 和 "xxxxx" 这两个参数,然后调用 build 方法完成构建。
<#assign reqVarName = className?uncap_first/>
创建了一个新的${className}对象,这个对象被赋值给了${reqVarName}。
<#if privateFields?? && (privateFields?size > 0)>
<#list privateFields as field>
${reqVarName} 设定为 ${field.exampleValue}(如果 ${field.exampleValue} 为空则为空字符串);
#list>
#if>
<#if javaApiMeta.needAuth>
String appAuthToken = "xxxx";
MeituanResponse<${javaApiMeta.responseClass}>meituanClient 调用了 API,调用的参数是 request 和 appAuthToken,调用的结果存储在 response 中。
<#else >
MeituanResponse<${javaApiMeta.responseClass}>meituanClient 调用了 API,其调用的结果为 response,而调用的请求是 request。
#if>
if (response.isSuccess()) {
<#if javaApiMeta.responseClass == "Void">
System.out.println("调用成功");
<#else>
response 获得的数据赋值给了 ${javaApiMeta.responseClass} 类型的 resp 。
System.out.println(resp);
#if>
} else {
System.out.println("调用失败");
}
使用 API 元数据和代码语料模板执行基于 DSL 的语言转换引擎后,生成的 API 调用示例代码呈现如下:
MeituanClient client = DefaultMeituanClient.builder(developerId, signKey).build();
//设置请求参数
创建了一个 OrderQueryByIdRequest 类型的 request 对象。
request 将订单 ID 设置为 1000224201796844308L 。
//调用接口
MeituanResponse response = client.invokeApi(req);
//处理接口返回
if(response.isSuccess()) {
response 中的 getData()方法获取到了 OrderQueryByIdResponse 类型的 orderResponse。
打印出 orderResponse 。
} else {
System.out.println("调用失败");
}
可以看出,我们生成的 API 调用示例代码能为开发者呈现每个请求参数赋值的示例逻辑,能有效降低开发者对接 API 时的理解成本。后续我们能够进一步优化 DSL 语料模板,在示例代码里增加对返回数据结构中各个字段的取值逻辑示范,以进一步降低开发者处理 API 返回数据时的理解和开发成本。
2.4 持续集成和持续发布
搞定参数富模型代码和调用示例代码的自动生成之后,下一步是利用持续集成和持续发布技术。这样就能保证开发者在任何时候都能够获取到最新版本的 SDK。在传统模式中,是由人工来进行编译、测试和上传发布 SDK 的。而这种传统模式下,开发者得到 SDK 版本更新的周期,短的话需要数周,长的话则需要数月。我们的目标是把这个周期缩短至分钟级别。当 SDK 的基础逻辑发生变更,或者 API 参数模型有任何改变时,凭借持续集成和持续发布的能力,能够在数分钟内把包含此变更的新版本 SDK 发布给开发者,让开发者使用。
我们依据美团自行研发的流水线引擎来推动 SDK 的持续集成与持续发布。流水线的执行能够被视为对生成 SDK 的“起始材料”逐步进行加工,最终把成品交付到线上的一个过程。首先通过下方的图来知晓整体的流程:
我们首先监听可能引发 SDK 需要发布的变更。其中包括利用机制来监听 API 元数据的变更,同时也通过 Git Hook 机制去监听 SDK 基础逻辑代码仓库分支的变更。一旦监听到有变更出现,就借助触发器来促使 SDK 持续集成和发布流水线开始运作。
流水线开始运作后,首先进行的是 SDK 构建组件的执行。SDK 构建组件会同时执行两个操作:
获取人工编写的 SDK 基础逻辑代码,接着完成静态代码检查;把 API 元数据拉取过来,然后自动生成参数富模型代码。
完成以上两个操作后,先进行代码合并,接着进行代码编译,然后把结果提交到流水线去执行下一个步骤。之后,由自动化测试组件来完成对 SDK 的单元测试以及端到端自动化测试,测试通过后,再将其提交到流水线执行下一个步骤。自动发布组件最终完成了一系列操作,包括 SDK 的打包、上传,以及生成下载链接和版本信息等,并且将最新版本的 SDK 发布到了官网,供开发者进行下载。
3. 结语
通过上述能力建设,我们打通了整个 SDK 自动生成的环节。我们以自动化方式完成了代码生成、构建、测试、集成、发布等一系列行为。最终在低人力投入的前提下,实现了持续向开发者交付最新版本 SDK 的目标。
最近半年数据进行对比后,我们能看出开发者使用 SDK 后,在接口对接环节遇到的疑难问题有明显减少。这基本达到了我们最初的目标,即提高开发者接入效率,同时降低平台研发和运营处理工单的成本。
我们接下来会计划持续完善 SDK 的代码自动生成逻辑,同时为 SDK 增添更多编程语言的支持,以便为接入美团开放平台的开发者带来更好的体验。
4. 写在后面
美团不久前独立申报的智慧生活国家新一代人工智能开放创新平台,已正式获得中华人民共和国科学技术部(简称“科技部”)的批复。此平台是美团的第一个国家级科研平台。
国家新一代人工智能开放创新平台被称作“人工智能国家队”。它聚焦于人工智能的重点细分领域,能够充分展现行业领军企业的引领示范作用,还能有效整合技术资源、产业链资源以及金融资源,持续输出人工智能的核心研发能力和服务能力,是重要的创新载体。在此之前,百度、阿里、腾讯等 15 家公司已先后获得批准进行建设。本次美团成功进行了申报。这一申报标志着美团的科研创新能力得到了国家层面的认可。并且,美团的科研创新能力达到了“国家队水平”。
5. 本文作者
王鸿来自美团到店事业群/餐饮 SaaS 事业部。
工作时间:8:00-18:00
电子邮件
扫码二维码
获取最新动态