应用程序编程接口( API)是一种计算接口,它定义了多个软件中介之间的交互。它定义了可以进行的调用或请求的类型,如何进行调用,应使用的数据格式,遵循的约定等。它还可以提供扩展机制,以便用户可以以各种方式扩展现有功能。在不同程度上。API 可以是完全定制的,特定于组件,也可以基于行业标准进行设计以确保互操作性。有些 API 必须记录在案,而其他 API 则经过设计,以便可以“查询”它们以确定支持的功能。
由于其他组件/系统仅依赖于 API,因此提供 API 的系统可以(理想地)在 API 的“后面”更改其内部详细信息,而不会影响其用户。
架构师的主要活动是做出正确的技术决策。选择何使得 API 是一项重要的技术决策。那么今天就看看 API 的选择问题。
正如上述的定义所述,API 提供了多个软件之间的交互。所以我们这里强调的是交互性。我们在使用任何的语言开发一个应用的时候,都会提供内部的基于该语言的 API,这种内部的 API 不是我们今天要讨论的内容,因为这种内部的交互不涉及到软件之间。我们今天要讨论的API主要要涉及到系统之间的交互。对于具体应用而言,更多是进程之间(本机),主机之间(本网络),服务之间(可能跨域广域网)的交互。
最早在 Unix/Linux 的编程领域,提供了进程间通信的手段,例如:管道,信号量,消息队列,套接字(Socket)等。如果你的应用是由不同语言编写的,那么这里只能选择 Socket 通信作为应用之间的 API 手段。但是 Socket 通信是一种非常低 Level 的通信手段,它以底层的数据包作为抽象和通信内容,很难维护和使用。当然还有一些其它的系统间通信的手段例如通过共享文件或者 FTP 的方式,同样面临着各种不便。我们希望提供一种更高级的交互手段,直接和我的应用的抽象交互,这些抽象可能是方法,函数和对象。于是就有了各种支撑这些需求的 API 技术。
早期的进程间通信技术包括:
- DCOM(Distributed Component Object Model)分布式组件对象模型,这个是微软的技术,只能用于 Windows 平台, 通过网络实现远程对象间的通信
- RMI(Remote Method Call) Java 的远程方法调用,这个是 Java 自己的 RPC,只能用于 Java 应用之间的远程调用。
- JNI Java 的本地接口, 支持 Java 应用调用本地方法,这个是跨越语言障碍的,但是仅仅局限于 Java 应用调用其它的本地应用,不具备互操作性,是个单项通道。
CORBA
在 1991 年,一种名叫 CORBA(Common Object Request Broker Architecture) 的技术出现了,主要涉及 C++ 和 Java。
CORBA 和之前提到的 DCOM 和 RMI 类似,都提供了远程的对象/方法调用,但是 CORBA 是一种与语言和实现无关的技术,我记得我们当时的测试脚本使用了 TCL,也有 CORBA 的实现,也就是说 CORBA 定了与语言解耦的系统间通信的标准。这个是它的最大的优势。那个年代的应用,采用 CORBA 作为系统间的通信手段非常普遍。
开发 CORAB 的过程从 IDL 的定义开始,用户通过 IDL 定义了对象,然后在 Server 端实现该对象的应用逻辑,在 Client 端调用该对象。
但是 CORBA 并非没有缺点,否则我们也不会很少再看见今天的应用用 CORAB 作为 API 的了。
它的主要问题是:
- 对象的生命周期管理比较复杂。远程对象的发现,创建和销毁都会带来问题
- 整个 CORAB 的架构比较复杂,看看它的架构图就知道了
总之,今天你要开发一个引用,除非要个已有系统交互,你应该不会选择 CORBA。
XML-RPC / SOAP
XML-RPC 发表于 1998 年,由 UserLand Software(UserLand Software)的 Dave Winer 及 Microsoft 共同发表。后来在新的功能不断被引入下,这个标准慢慢演变成为今日的 SOAP 协议。
下面是一个 XML-RPC 的请求/响应的例子
<?xml version="1.0"?>
<methodCall>
<methodName>examples.getStateName</methodName>
<params>
<param>
<value><i4>40</i4></value>
</param>
</params>
</methodCall>
<?xml version="1.0"?>
<methodResponse>
<params>
<param>
<value><string>South Dakota</string></value>
</param>
</params>
</methodResponse>
SOAP 是 Simple Object Access Protocol 的缩写。SOAP 为 Web 服务提供了 Web 服务协议栈的 Messaging Protocol 层。它是一个基于 XML 的协议,由三部分组成:
- 一个信封,它定义了消息结构以及如何处理它
- 一组用于表达应用程序定义的数据类型实例的编码规则
- 表示过程调用和响应的约定
SOAP具有三个主要特征:
- 可扩展性(安全性和WS-Addressing在开发中)
- 中立性(SOAP可以通过HTTP,SMTP,TCP,UDP等任何协议进行操作)
- 独立性(SOAP允许任何编程语言)
作为SOAP过程可以执行的操作的示例,应用程序可以将SOAP请求发送到启用了带有搜索参数的Web服务的服务器(例如,房地产价格数据库)。然后,服务器返回SOAP响应(包含结果数据的XML格式的文档),例如价格,位置,功能。由于生成的数据采用标准化的机器可解析格式,因此发出请求的应用程序可以直接将其集成。
SOAP体系结构由以下几层规范组成:
- 讯息格式
- 邮件交换模式(MEP)
- 底层传输协议绑定
- 消息处理模型
- 协议可扩展性
这里是一个SOAP消息的例子:
POST /InStock HTTP/1.1
Host: www.example.org
Content-Type: application/soap+xml; charset=utf-8
Content-Length: 299
SOAPAction: "http://www.w3.org/2003/05/soap-envelope"
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:m="http://www.example.org">
<soap:Header>
</soap:Header>
<soap:Body>
<m:GetStockPrice>
<m:StockName>T</m:StockName>
</m:GetStockPrice>
</soap:Body>
</soap:Envelope>
相比较 XML-RPC,他的功能更多,当然消息结构也更复杂。
SOAP 是 W3C 推荐的 Webservice 标准,一度也是非常的流行,但是我们看到基于 XML 的消息比较复杂,消息本身因为 XML 的原因,有相当多的开销,于是后面又有了基于 JSON 的 RPC 格式(有关 JSON 的操作,请参考本站提供的 JSON 校验工具)。但总的来说,SOAP 也已经是昨日黄花,当今的应用构建,你选它的概率应该也不大了。
REST
REST 是当今最为流行的 API,因为大量的 Web 应用采用 REST 作为其 API 的选择。
REST 是 Representational State Transfer 的缩写,是 Roy Thomas Fielding 博士于 2000 年在他的博士论文中提出来的一种万维网软件架构风格,目的是便于不同软件/程序在网络(例如互联网)中互相传递信息。
表现层状态转换是根基于超文本传输协议(HTTP)之上而确定的一组约束和属性,是一种设计提供万维网络服务的软件构建风格。符合或兼容于这种架构风格(简称为 REST 或 RESTful)的网络服务,允许客户端发出以统一资源标识符访问和操作网络资源的请求,而与预先定义好的无状态操作集一致化。因此表现层状态转换提供了在互联网络的计算系统之间,彼此资源可交互使用的协作性质(interoperability)。相对于其它种类的网络服务,例如 SOAP 服务,则是以本身所定义的操作集,来访问网络上的资源。
目前在三种主流的 Web 服务实现方案中,因为 REST 模式与复杂的 SOAP 和 XML-RPC 相比更加简洁,越来越多的 Web 服务开始采用 REST 风格设计和实现。所以我么可以看到软件的发展,大体是从复杂变得简单,只有简单的东西才会变的更有生命力。
为了使任何应用程序真正实现 RESTful,必须遵循六个体系结构约束:
- 统一接口:意味着必须向Web应用程序中的 API 使用者提供 API 接口。
- 客户端服务器:客户端和服务器必须彼此独立,并且客户端应仅知道资源的 URI。
- 无状态:服务器不得存储与客户端请求相关的任何内容。客户端负责维护应用程序的状态。
- 可缓存的:资源必须可缓存。
- 分层系统:体系结构必须是分层的,这意味着体系结构的组件可以位于多个服务器中。
- 按需代码:客户端必须能够获取可执行代码作为响应。这是一个可选约束。
基于 REST 的 Web 服务被称为 RESTful Web 服务,在这些应用程序中,每个组件都是一种资源,可以使用HTTP标准方法通过公共接口访问这些资源。以下四种 HTTP 方法通常用于基于 REST 的体系结构中:
- GET - 对资源的只读访问。
- POST — 创建一个新资源。
- DELETE — 删除资源。
- PUT - 更新现有资源/创建新资源。
RESTFul 风格 API 所有的操作都是一个动词,对应 HTTP 请求的一种类型。每一个操作都定义了对操作的资源的某种行为。这种抽象,特别适合相当多的 Web 应用,后台是一个数据库,每一个 REST 的端点对应了一张数据库的表,很自然的利用 REST 操作来实现表的增删查改。
当然,RESTFul 的风格也有它的不足:
- 不是所有的应用操作都可以用资源的增删查改来对应,在实际的开发中经常会需要把一个操作映射为一个资源这种不伦不类的行为。
- REST 是同步服务,如果需要可能要引入回调机制,例如 Webhook。
- REST 只提供客户端调用服务器的选项,不支持服务器端发起请求。
于是新的 API 类型会出现来解决这些问题。
GraphQL
GraphQL 是一个开源的 API 数据查询和操作语言及实现为了实现上述操作的相应运行环境。2012年,GraphQL 由 Facebook 内部开发,并于 2015 年公开公布。2018年11月7日,Facebook 将 GraphQL 项目转移到新成立的 GraphQL 基金会 。
GraphQL 规范概述了 5 条设计原则,这使其成为现代前端开发的精心设计的解决方案。让我们研究一下 GraphQL 的设计原则。
- 查询是分层结构的,具有分层和嵌套字段,查询与响应数据一对一匹配。查询和响应的形状像树,可以查询每个项目的其他嵌套字段。
- 该结构以产品为中心,着重于前端希望如何接收数据,并构建交付所需的运行时。这样一来,就可以向后端请求一个所需的所有数据,然后让服务器根据GraphQL的规范从不同的端点获取数据。
- 它使用特定于应用程序的类型系统,使开发人员能够确保查询使用有效类型,并且在执行之前在语法上正确。
- GraphQL 查询是在客户端指定的,因此客户端确切知道它将以什么格式接收数据。
- 带有 GraphQL 的服务器结构必须是自省的,或者可由 GraphQL 本身查询。这将启用功能强大的开发人员工具,例如 GraphQL 或 GraphQL Playground,这两种工具都将使开发人员能够准确查看哪些查询和字段可供他们在服务器中使用.
像 RESTful API 一样,GraphQL API 旨在处理 HTTP 请求并提供对这些请求的响应。但是,相似之处到此结束。在 REST API 建立在请求方法和端点之间的连接上的情况下,GraphQL API 设计为仅使用一个始终通过 POST 请求查询的端点,通常使用 URL yourdomain.com/graphql(参考:GraphQL 入门指南)。
达到 GraphQL 端点后,客户端请求的负担将完全在请求主体内处理。该请求主体必须遵守 GraphQL 规范,并且 API 必须具有适当的服务器端逻辑来处理这些请求并提供适当的响应。与 RESTful API 相比,这提供了更流畅的客户端体验,后者可能要求客户端对多个数据进行多次请求,并在数据返回后进行操作。
如上图的例子,用户通过 RESTFul 的 API 来请求数据,需要两个 GET 请求,先获取 Assets,再通过 AssetID 获取 comments。而通过 GraphQL,用户只需要描述需要请求的数据的结构和条件,就可以通过一个请求获取全部所需要的数据,简化了客户端与服务器的交互。
GraphQL 提供的性能优于 REST API,可以为前端开发人员带来回报。使用 GraphQL 规范创建服务器可能需要更多设置和编写预测性服务器端逻辑来解析和处理请求。尽管 GraphQL 的安装成本可能会高于传统的 REST 架构,但更具可维护性的代码,强大的开发工具以及简化的客户端查询,这些都是不错的收益。
除了灵活性这个最大的优点外,GraphQL 还有以下的优点:
- 声明性的数据获取,避免了客户端和服务器端的额外交互
- 优秀的开发体验,不需要版本控制,因为引入新的字段不会影响到 API 查询。同时客户端和服务器端的团队可以并行的独立工作。
- 强类型的 GraphQL 模式使得代码可预测,并及早发现错误。
当然,GraphQL 也不是没有缺点:
- 使用 GraphQL,如果您需要查找有关列表或记录集合的信息,则处理起来会很棘手。例如,如果您想获取包含其地址的用户列表的详细信息,则它将执行 n + 1 个查询。一个用于用户列表,然后 n 查询每个用户的地址。现在它会严重影响性能,因此必须非常小心地处理它。
- 很难缓存,缓存 API 响应的目的主要是为了更快地从将来的请求中获取响应。与 GraphQL 不同,RESTful API 可以利用 HTTP 规范中内置的缓存。正如前面提到的,GraphQL 查询可以请求资源的任何字段,因此缓存本质上是困难的。
gRPC
gRPC 是一个开源的远程过程调用框架,用于在服务之间进行高性能的通信。这是将以不同语言编写的服务与可插拔支持(用于负载平衡,跟踪,运行状况检查和身份验证)相连接的有效方法。默认情况下,gRPC 使用 Protobuf(协议缓冲区)序列化结构化数据。通常,对于微服务体系结构,gRPC 被认为是 REST 协议的更好替代方案。gRPC 中的 g 可以归因于最初开发该技术的 Google。
gRPC 是对传统 RPC 框架的改编。那么,它与现有的 RPC 框架有何不同?
最重要的区别是 gRPC 使用 protobuf 协议缓冲区作为接口定义语言进行序列化和通信,而不是 JSON / XML。协议缓冲区可以描述数据的结构,并且可以从该描述中生成代码,以生成或解析表示结构化数据的字节流。这就是为什么 gRPC 首选多语言(使用不同技术实现)的 Web 应用程序的原因。二进制数据格式使通信更轻松。gRPC 也可以与其他数据格式一起使用,但是首选的是 protobuf。
同样,gRPC 建立在 HTTP/2 之上,它支持双向通信以及传统的请求/响应。gRPC 允许服务器和客户端之间的松散耦合。在实践中,客户端打开与gRPC服务器的长期连接,并且将为每个 RPC 调用打开一个新的 HTTP/2 流。
与使用 JSON(主要是JSON)的 REST 不同,gRPC 使用 Protobuf,这是编码数据的更好方法。由于 JSON 是基于文本的格式,因此它比 protobuf 格式的压缩数据要重得多。与 REST 相比,gRPC 的另一个显着改进是它使用 HTTP 2 作为其传输协议。REST 使用的 HTTP 1.1 基本上是一个请求-响应模型。gRPC 利用 HTTP 2 的双向通信功能以及传统的响应请求结构。在 HTTP 1.1 中,当多个请求来自多个客户端时,它们将被一一处理。这会降低系统速度。HTTP 2 允许多路复用,因此可以同时处理多个请求和响应。
gRPC 的开发模式和之前提到的 CORBA 有些类似。Protobuf 充当了 IDL 的角色,然后利用工具生成各种语言的代码,最后在生成的代码上实现服务器端和客户端的逻辑。
gRPC的优点是:
- 出色的性能,因为采用 protobuf 编码和 HTTP/2
- 支持服务器端和客户端的双向通信
- 易用,相比 REST 开发,需要更少的代码
缺点:
- 更陡峭的学习曲线
- 支持的语言的种类没有 REST 多,当然它还在发展中
- 因为需要 Protobuf 的编译,这带来了服务器和客户端一定的耦合,因为接口变动的时候需要重新编译生成代码。而使用 REST 的话,基于不同的工具链可能由不同的解决方案
因为其高性能,gRPC 更适合被用于系统内部组件的通信选择。
在下图的微服务架构中,对外的服务采用了 REST 或者 GraphQL 的 API,而内部微服务之间使用的是 gRPC。