时下随着架构解耦和微服务架构的兴起,每一个开发者都不可避免要用到REST构建API和用REST交互。然而我们真正了解并善用了当代REST和超媒体的好处么?本文就和大家一起学习了,2020年的REST和超媒体。
概述
REST被认为是一堆抽象层,可帮助减少与客户端开发人员的交流,同时使能够构建更坚固和一致的应用程序。
这可以通过几种方式来实现,主要是依靠REST API存在的协议的现有语义,通常是HTTP。在HTTP中构建REST API就要遵循现有的HTTP语义来实现REST提出要实现的概念,而不是发明自己的约定。
客户端可以查询Retry-After标头,而不是编写一堆代码来了解API可能会响应的每个应用程序错误,并自己处理错误的代码。
开发者抱怨REST很难,因为必须要了解HTTP方法及其含义以及不同的状态代码。虽然学习HTTP可能需要一些时间,但HTTP的约定与庞大的工具生态系统共享。
开发者一致对这些技术很排斥,很多时候他们更喜欢RPC。对于许多开发人员来说,可以在互联网上运行这些API就足矣,他们可以在上面自己添加操作和约定,这样他们虽然也获得了一些需要的功能CRUD(POST, GET, PUT, DELETE = Create, Read, Update, Delete)增删改查,并丢了REST架构可以提供的大量的好处。
"REST是RPC之上的一堆抽象层"吗?
Richardson的成熟度模型结构图示如下:
这是Martin Fowler在介绍《Richardson成熟度模型》的文章的总结图示。该模型以Leonard Richardson的名字命名,讨论API的成熟度。
该图有一些常见的问题,主要是顶部是" Glory of REST",底部乱成一团。不幸的是,这使REST API看起来像是个迷宫,而其他所有事物都很乱的。
REST的带来好处是很明显的:
- 网络上的所有事物都可被抽象为资源(Unify);
- 每个资源都有统一唯一的资源标识符(URI);
- 使用标准方法操作资源;
- 所有的操作都是无状态的;
- 可以通过缓存来提高性能。
另一个问题是Martin谈论的是Plain Old XML,如今,有些人谈论的是POJO(Plain old JSON Objects)。
所有API开发人员都可以使用API规范(用于描述数据模型的元数据),无论他们的范例或选择的实现方式如何,因此都应将其考虑在内。REST人员使用JSON Schema,gRPC人员使用Protobuf,而GraphQL用户具有GraphQL类型。
改进Richardson成熟度模型
我们对上面提到的Richardson成熟度模型进行改进,每层简要地提到了它启用的一些功能。
RPC
真正遇到的问题是,最基本形式的RPC通常会忽略许多HTTP概念。它不会使用HTTP的统一接口及其完整语义,也不使用HTTP作为传输协议,而仅使用传输方面。传输协议可以帮助您知道何时或是否需要发出请求,而不仅仅是在网络上传送数据。大多数RPC实现都与单个端点交互,并且大多数交互都使用单个HTTP方法。很少有通用的HTTP约定只能用于最基本的RPC。
如果RPC遵循特定标准,则可以使用为该标准构建的工具,但是通用HTTP约定不适用。
资源
这里有两个常见的困惑,首先这不是关于拥有/bikes, /bikes/abc123而是经常使用的标准集合,复数和资源CRUD模式。
从技术上讲,资源与端点是一样的东西,但是有一个故意的区别。端点通常被认为更像是函数,其目的是在想做某事时调用一个函数,但这又是传输的大多数传输方式,通常是RPC思维的标志:调用某事并执行一个事情。
资源更像是标识符,它是在特定位置的唯一事物,可以被该事物识别。它是HTTP世界中最终的唯一标识符,尽管可能会有具有相同字母/数字ID的不同产品,也有可能UUID也会冲突。
URI(统一资源标识符)是拥有适用于所有内容的唯一。这样我们可以将特定的标头添加到不同的资源中,这些标头可以与响应一起作为元数据存储。
这样资源可以声明自己的可缓存性,这是REST谈论的重要内容之一。
缓存约束要求对请求的响应中的数据被隐式或显式标记为可缓存或不可缓存。如果响应是可缓存的,则授予客户端缓存以将响应数据重新用于以后的等效请求的权限。
添加缓存约束的优势是可以部分或完全消除某些交互,从而通过减少一系列交互的平均延迟来提高效率,提高系统的可伸缩性和用户感知的性能。
HTTP/1.1中缓存的目标是通过重用先前的响应消息来满足当前请求,从而显著提高性能。如果可以在没有"验证"的情况下重用响应,则存储的响应被视为"新鲜的"(与原始服务器进行检查,以了解缓存的响应是否对该请求仍然有效)。因此,每次重新使用响应时,新响应都可以减少延迟和网络开销。当缓存的响应不"新鲜"时,如果可以通过验证将其刷新或原始不可用,则该响应可以重用。
如果具有唯一的URI,则在HTTP/2中也无痛升级使用。
HTTP方法
方法为请求中发生的事情的类型添加了许多重要的语义。如果使用了缓存,则缓存组件将知道它可以缓存GET请求,但是如果对同一个资源执行POST或DELETE操作,它将知道它应该避开它。
像自动重试这样的客户端逻辑现在成为可能。当API花费很长时间进行响应时,重试可以提供帮助,客户端应用程序可能会针对请求进行保释,然后重试。使用GET几乎没有什么缺点,它是幂等请求,不应具有任何破坏性动作。
重试POST可能很危险,因为可能在达到超时之前,它可能会更改数据库中的某些记录,发送一些电子邮件,从信用卡中收取费用等。
PUT和PATCH会很好,PUT也是幂等的,只会消除结果,并且PATCH通常具有"from"和"to",这意味着如果第二次发出请求,则"from"可能会不匹配。
对RPC的忠实拥护者,!gRPC"HTTP桥"添加了这两个抽象层,使其更加像HTTPish。
超媒体控件
超媒体控件是"超媒体作为应用程序状态引擎"(HATEOAS)的简写,它是一个非常简单的概念。API不仅仅是HTTP上的数据存储,而是成为HTTP上的状态机。它仍然有数据,但是它也可以以自我描述的方式提供"下一个可用动作"。
考虑一张发票,说它是可付款的,而不是需要根据缺少来确定是否可以付款paid_date,或者可能有status: pending,但是可能会添加新的状态并等待处理并不意味着可以付款。客户应用程序中断或需要版本控制,这两者都会浪费开发人员时间和公司资金。
如果发票是应付账款,则会显示一个名为"付款"的链接,这表示客户端应用程序知道何时付款,并且只要使用了良好的超媒体格式,客户端应用程序就会知道如何付款,因为控件可以向服务器发送HTTP请求之前验证数据客户端,给出哪些数据是必须要提供的。
超媒体的最基本层次是将链接推到响应主体中,但是客户端必须做很多验证工作才能弄清楚下一步该做什么。此前,只会是:"你有一个URL和一个链接关系,这是一个很好的开始",但是如今,有很多流行的Hypermedia Formats使这一切变得容易得多。
REST
大多数自称为REST的API都没有最后一层,这意味着它们使用的仅仅是RESTish,或者仅仅是HTTP API。因为没有超媒体控件,就不能叫REST API,这很重要。
可能开发人员根本不知道HATEOAS(超媒体即应用状态引擎或超媒体约束)是什么。还有一些人知道,但是认为HATEOAS就是要强迫进行HTTP请求负载以获取相同数量的数据。这时他们考虑的是传输而不是转移,到了时下快要普及的HTTP/2,即使需要进行"更多调用",性能影响也可以忽略不计。
即使到了REST这一层,也不意味着API会在所有方面都永远可靠且完美。粗劣的资源设计会使任何API都难以使用,不论你使用的是哪种范例。GraphQL开发人员现在开始注意到这一点 。
专注于满足客户需求的模型设计非常重要,API会随着时间的推移而发展,修剪掉无用的数据,并创建复合资源以很大程度地减少不必要的网络延误。JSON Schema也刚刚弃用了一个关键字,这可以使API的开发变得更加容易。
超媒体和gRPC或GraphQL
在谈论超媒体控件时,常有这样的观点,比如 "这不仅是REST可以做的事情,如果使用HTTP Bridge并添加了链接,gRPC可以做到这一点!"
一段时间以来,著名的GraphQL开发人员一直在尝试找到一种将超媒体控件引入GraphQL的方法。如果他们能解决这个问题,GraphQL不会准确地遵循下面这个图,但我们可以调用query和mutation他们分享有或没有语义足够靠近HTTP方法和唯一缺少的是资源(URI)来。
对于GraphQL,缺少URI是一个更大的问题,这几乎破坏了他们直接使用HTTP/2 Server Push的机会,他们不需要向供应商寻求特定的解决方案,例如Apollo订阅和其他非标准@defer类型的东西。
总结
无论如何,API并不总是需要超媒体控件。例如,全栈开发人员经常认为REST浪费时间,因为他们只是想要查询数据库并将信息传递到表示层。他们不需要将缓存控件附加到消息本身中,他们只需要在客户端应用程序中设置缓存即可,该客户端应用程序可能在其计算机上的另一个窗口中打开。他们知道何时使用重试,因为他们编写了应用程序代码并知道它们的含义,因此他们不在乎是否使用了HTTP语义。这些开发人员与试图向可能位于不同楼层或不同大陆的各种客户团队提供一致功能的开发人员完全没有共同之处,在这些客户团队中,传达变更或如何推断状态可能是一个代价高昂的问题。这些团队可能正在使用各种网络和客户端工具,例如缓存中间件,服务监控,代理检查,并且不能限制他们可以使用的工具,因为这可能会使他们丢掉业务。
两者之间还有其他所有完全有效的方案。并不是所有的汽车都需要防弹玻璃,并非所有的对话都要有麦克风,不是所有的内衣都需要有吊带。同理,也不是所有的API都需要是REST。