REST API 设计的原则与最佳实践

746 阅读7分钟

这篇关于最佳实践的文章,其目标读者是那些希望创建一个跨多个服务套件并提供高可靠性和一致性RESTful Web服务的开发人员。

下面是完整的关系图,方便你去理解REST API Design的原则、方法和最佳实践。

1_lFGlOSW19H184tUt9DhvUg@2x.png

现在,让我们从图中的每个模块的原理开始阐述。

六项原则/约束

  1. 客户机-服务器(Client-Server): 关注点分离是客户机-服务器约束的背后原则。通过将用户界面的关注点与数据存储的关注点进行分离,可以提高用户界面对多个平台的可移植性,并且通过简化服务器组件来提高可伸缩性。

  2. 无状态化(Stateless): 通信必须是无状态的,就像 client-stateless-server(CSS) 模型那样,所以由客户端发向服务端的每个请求都必须携带能解释本次请求的所有信息,因此,会话状态完全保存在客户端上。

  3. 可缓存的(Cacheable): 为了提高网络效率,我们可以通过添加缓存约束来形成client-cache-stateless-server结构。缓存约束要求,对于可缓存和不可缓存的请求,数据要分别以隐式和显式的方式进行响应。如果响应是可缓存的,那么它将会被客户端重用于后续相同的请求。

  4. 系统分层(Layered System): 客户端通常不知道它与服务端的连接是直连还是经过中间服务器代理。中间服务器可以通过启用负载平衡和提供共享缓存来提高系统的可伸缩性。同时,层次系统还可以用来强制执行安全策略。

  5. 按需编码(Code-on-Demand): REST服务允许客户端通过下载相关可执行代码和脚本来扩展自身的功能。通过减少一些需要预实现的逻辑来简化客户端的加载,这部分逻辑可以在后续进行加载,从而提高系统的可扩展性。

  6. 统一接口(Uniform Interface): 通过将软件工程的通用性原则应用于组件接口,从而简化整个系统架构,提高了交互的透明度。实现与它们提供的服务解耦,这提倡独立的可演化性。RESTful应用需要符合四个核心的约束:资源的识别(identification of resources)通过表述操作资源(manipulation of resources through representations)自描述的消息(self-descriptive messages)超媒体作为应用状态引擎(hypermedia as the engine of application state)

  7. 自描述信息(Self-descriptive Message): 每条消息都包含足够的信息来描述如何处理该消息。

  8. 基于资源的(Resource-Based): 使用URI作为资源标识符在请求中标识各个资源。 资源本身在概念上与返回给客户端的表示分开。

  9. 通过资源表示标识来操作资源(Manipulation of Resources Through Representations): 当客户端持有一个资源的表示(包括资源本身及附加的任何元数据)时,它有足够的信息来修改或删除服务器上的资源,前提是它具有这样做的权限。

  10. 超媒体作为应用程序状态引擎(Hypermedia as the Engine of Application State (HATEOAS)): 客户端通过构造请求的body内容,查询字符串参数,请求header和请求的URI(资源名称)来传递状态。 服务通过响应的body,状态码和header向客户提供状态。

最佳实践

现在,让我们来了解REST基本的的最佳实践,这是每个工程师都应该知道的。

1_Iq0hbP3941PyYughu1mFQA.jpeg

  1. 保持简洁度与细粒度(Keep it Simple and Fine-Grained): 创建用来模拟系统底层应用程序域或系统数据库架构的api。最终,您将使用聚合来利用多个底层资源以减少空闲的服务数量。
  2. 过滤 & 排序(Filtering & Ordering): 对于大型数据集,从带宽的角度来说,限制其返回的数据量是至关重要的。另外,我们可以在响应头上设置一些字段来限制返回的数据量,以及根据请求查询特定的值并对返回的数据进行排序。
  3. 版本控制(Filtering & Ordering):API开发过程中,有很多方法会破坏约束条件并对客户端产生负面影响。如果你不确定更改的结果,最好保持谨慎并考虑版本控制。在决定新版本是否合适,或现有修改是否可行时,多考虑几个因素。由于维护多个版本会变得非常麻烦、复杂、容易出错且成本很高,所以对于任何给定的资源,你都不应该维护超过两个版本。
  4. 缓存(Cache): 缓存通过消除对系统各个层级的数据检索远程调用来增强了可伸缩性。服务端可以通过设置诸如Cache-ControlExpiresPragmaLast-Modified等响应头来提高缓存能力。
  5. 分页(Pagination): 构建REST应用的其中一个原则是保持连通性—使用超媒体链接。与此同时,即便没有它们,服务仍然是可用的。当链接在响应中返回时,api变得更具有自描述性。对于支持分页的响应返回的数据集合来说,“first”“last”“next”“prev”这样的链接至少是有益的。
  6. 资源命名(Resource-Naming): 当资源拥有良好的命名时,API是直观易理解且便于使用的。相反,资源的胡乱命名会使得API显得笨拙、难以使用与理解。RESTful api是面向用户的,uri的名称和结构应该直观表现出其含义。通常情况下,很难知道数据边界具体是什么,但在了解了数据的结构之后,你或许已经清楚,将数据如何表示并返回给你的用户。为用户设计,而不是为数据设计。
    • 多元化(Pluralization): 公认的实践是在节点名中使用复数,以保持所有HTTP方法之间的API uri一致。原因是“客户”是服务套件中的一个集合,ID(例如33245)引用集合中的一个客户。
  7. 监控(Monitoring): 添加各种监控锚点以提高API的质量或性能。数据点可以是响应时间(P50p90P99)、状态码(5XX4XX等)、网络带宽等等。
  8. 安全性(Security):
    • 授权 / 认证 (Authorization / Authentication): 服务端的鉴权与其他应用鉴权没什么不同,关键都在于,发起请求的人是否有权获取这个资源。
    • 跨域资源共享 (CORS): 在服务器上实现CORS就像在响应中发送一个额外的HTTP报头一样简单,例如Access-Control-Allow-OriginAccess-Control-Allow-Credentials 等等。
    • 传输层安全 (TLS): 所有身份验证都应该使用SSLOAuth2需要授权服务器和访问令牌凭据来使用TLS
    • 幂等性 (Idempotence): 一种操作,不管执行一次还是多次,产生的结果都相同。根据上下文的不同,它可能有不同的含义。例如,对于具有副作用的方法或子程序调用,表示修改后的状态在第一次调用后保持不变。
    • 输入校验 (Input Validation): 对服务器收到的一切输入进行校验。只接受“已知”的正确输入并拒绝错误输入,防止SQLNoSQL注入,将消息大小限制为字段的确切长度,服务端只显示通用错误消息,等等。
    • 速率限制 (Rate limiting): 这是一种限制网络流量的策略,用来限制某个用户在某个时间段内重复某个操作的频率,例如限制频繁登录等等。
    • 日志 (Logging): 请确保日志系统不会记录任何个人验证信息(PII: Personally identifiable information )

参考文献