前言:
架构师的主要活动是做出正确的技术决策。选择合适的API是一项重要的技术决策。那么今天就看看API的选择问题。
应用程序编程接口(API)是一种计算接口,它定义了多个软件中介之间的交互。它定义了可以进行的调用或请求的类型,如何进行调用,应使用的数据格式,遵循的约定等。它还可以提供扩展机制,以便用户可以以各种方式扩展现有功能。在不同程度上。API可以是完全定制的,特定于组件,也可以基于行业标准进行设计以确保互操作性。有些API必须记录在案,而其它API则经过设计,以便可以“查询”它们以确定支持的功能。由于其他组件/系统仅依赖于API,因此提供API的系统可以(理想地)在API的“后面”更改其内部详细信息,而不会影响其用户。
正如上述的定义所述,API提供了多个软件之间的交互。所以我们这里强调的是交互性。我们在使用任何的语言开发一个应用的时候,都会提供内部的基于该语言的API,这种内部的API不是我们今天要讨论的内容,因为这种内部的交互不涉及到软件之间。我们今天要讨论的API主要要涉及到系统之间的交互。对于具体应用而言,更多是进程之间(本机),主机之间(本网络),服务之间(可能跨域广域网)的交互。
目录:
1、CORBA
2、XML-RPC / SOAP
3、REST
4、GraphQL
5、gRPC
最早在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应用调用其它的本地应用,不具备互操作性,是个单向通道。
1.CORBA
在1991年一种名叫CORBA ( Common Object Request Broker Architecture ) 的技术出现了,我记得我的第一份工作是一个电信网管系统的开发,我们就是利用CORBA来实现不同的系统之间的通信。主要涉及C++和Java。
CORBA和之前提到的DCOM和RMI类似,都提供了远程的对象/方法调用,但是CORBA是一种与语言和实现无关的技术,我记得我们当时的测试脚本使用了TCL,也有CORBA的实现,也就是说CORBA定了与语言解耦的系统间通信的标准。这个是它的最大的优势。那个年代的应用,采用CORBA作为系统间的通信手段非常普遍。
开发CORAB的过程从IDL的定义开始,用户通过IDL定义了对象,然后在Server端实现该对象的应用逻辑,在Client端调用该对象。
但是CORBA并非没有缺点,否则我们也不会很少再看见今天的应用用CORAB作为API的了。他的主要问题是:
- 对象的生命周期管理比较复杂。远程对象的发现,创建和销毁都会带来问题
- 整个CORAB的架构比较复杂,看看它的架构图就知道了
总之,今天你要开发一个引用,除非要个已有系统交互,你应该不会选择CORBA。
2.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格式。但总的来说,SOAP也已经是昨日黄花,当今的应用构建,你选它的概率应该也不大了。
3.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类型会出现来解决这些问题。
4.GraphQL
GraphQL是一个开源的API数据查询和操作语言及实现为了实现上述操作的相应运行环境。2012年,GraphQL由Facebook内部开发,2015年公开公布。2018年11月7日,Facebook将GraphQL项目转移到新成立的GraphQL基金会 。
GraphQL规范概述了5条设计原则,这使其成为现代前端开发的精心设计的解决方案。让我们研究一下GraphQL的设计原则。
- 查询是分层结构的,具有分层和嵌套字段,查询与响应数据一对一匹配。查询和响应的形状像树,可以查询每个项目的其他嵌套字段。
- 该结构以产品为中心,着重于前端希望如何接收数据,并构建交付所需的运行时。这样一来,就可以向后端请求一个所需的所有数据,然后让服务器根据GraphQL的规范从不同的端点获取数据。
- 它使用特定于应用程序的类型系统,使开发人员能够确保查询使用有效类型,并且在执行之前在语法上正确。
- GraphQL查询是在客户端指定的,因此客户端确切知道它将以什么格式接收数据。
- 带有GraphQL的服务器结构必须是自包含的,或者可由GraphQL本身查询。这将启用功能强大的开发人员工具,例如GraphiQL或GraphQL Playground,这两种工具都将使开发人员能够准确查看哪些查询和字段可供他们在服务器中使用。
像RESTful API一样,GraphQL API旨在处理HTTP请求并提供对这些请求的响应。但是,相似之处到此结束。在REST API建立在请求方法和端点之间的连接上的情况下,GraphQL API设计为仅使用一个始终通过POST请求查询的端点,通常使用URL yourdomain.com/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查询可以请求资源的任何字段,因此缓存本质上是困难的。
5.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流。
如上图所示,gRPC支持不同模式的客户端和服务器端的通信方式,极大的方便了不同的互操作能力。
与使用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。
6.总结
好了,看了这么多的API选择之后,我们做一个小结。
系统间的API选项经过多年的发展,现阶段的主流是RESTful API,gRPC 和GraphQL。具体怎么选择,要结合你的业务上下文,我的推荐是:
- 对外提供的公开服务,首选RESTFul API,因为它非常成熟稳定和流行,语言和工具链的支持都很好。
- 如果你希望加速应用的客户端开发,GraphQL是个不错的选择,提供良好的性能和灵活性
- 如果你的应用特别看重性能,而且主要是内部系统之间的交互,建议考虑gRPC