所有文章 > API设计 > API设计:从REST到RPC

API设计:从REST到RPC

RESTful API

RESTful API是一种设计思想,它与具体的实现方式或技术无关。但是,目前最流行的RESTful API大多采用JSON数据格式,并且使用HTTP方法。一个典型的RESTful API应具备以下特征:

  1. 使用HTTP协议:采用HTTP方法 like GET、POST、PUT、DELETE 来对资源进行操作。
  2. 无状态:服务端不会储存客户端状态,每次请求必须包含所有必要信息。
  3. 基于资源:URL代表资源,并使用HTTP动词来表示对这些资源的具体操作。
  4. HATEOAS:通过超链接来连接资源,客户端不需要知道资源之间的关系。
  5. 可选可缓存:数据可以被客户端缓存,以提高性能。
  6. 标准接口:提供标准的接口以操作资源,结合过滤器或标识以操作指定资源

一个简单的RESTful API例子:

获取用户列表:
GET /users
获取指定用户:
GET /users/1
创建用户:

POST /users
{
"name": "John",
"age": 30
}
更新用户:
PUT /users/1
{
"name": "John",
"age": 31
}
删除用户:
DELETE /users/1

它的优点在于:

  1. 易于理解和简单
  2. 具有良好的可扩展性
  3. 可以跨平台使用
  4. 允许集成缓存机制
  5. 支持非常高效的数据交换

标准接口

RESTful API中,我们使用标准的接口比如 /users 来操作资源,然后通过过滤条件或资源标识来指定操作哪些资源。例如:

获取所有用户:  GET /users
创建一个产品: POST /users?type=product
更新一条订单: PUT /users/1?type=order
删除一篇文章: DELETE /users/2?type=post

这样设计的好处是接口简单易用,客户端可以通过一套标准接口来访问不同类型的资源,只需要在请求中提供必要的过滤条件或资源标识。

HATEOAS

HATEOAS是代表”Hypermedia As The Engine Of Application State”的首字母缩写。它是RESTful API的一个重要原则,意思是应用状态应驱动由超媒体来定义和控制。也就是说,RESTful API应该提供一系列超链接来连接相关资源,并通过这些链接来定义客户端的工作流程与导航方式。客户端不需要事先知道这些链接,只需要与API进行交互,并根据获得的链接来获取更多信息或调整状态。一个典型的例子是Github的API。例如,当我们获取一个用户资源时:

{
"login": "octocat",
"id": 1,
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "",
"url": "https://api.github.com/users/octocat",
"html_url": "https://github.com/octocat",
"followers_url": "https://api.github.com/users/octocat/followers",
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
"organizations_url": "https://api.github.com/users/octocat/orgs",
"repos_url": "https://api.github.com/users/octocat/repos",
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/octocat/received_events",
"type": "User",
"site_admin": false
}

这个用户资源提供了许多链接,分别指向:

  • 关注者列表(followers_url)
  • 被关注者列表(following_url)
  • 用户的gists(gists_url)
  • 用户star的仓库列表(starred_url)
  • 用户的订阅列表(subscriptions_url)
  • 用户所在的组织列表(organizations_url)
  • 用户所有的仓库列表(repos_url)
  • 用户的事件列表(events_url)
  • 用户收到的事件列表(receivedeventsurl) 因此,客户端可以通过这些链接来获取更多相关信息,调整状态,而不需要硬编码这些关系或链接。这就是HATEOAS原则的应用—通过超媒体(这里指链接)来定义应用状态(用户及相关资源的完整信息)。所以,HATEOAS的主要特征是:
  1. API响应中提供其他相关资源的链接
  2. 客户端可以通过这些链接来获取更多信息或改变状态
  3. 客户端不需要事先 hardcoded 这些链接,只需要动态的根据获得的链接来导航

RPC接口的设计

设计微服务RPC接口时,可以遵循RESTful的理念和规范,使RPC接口易于理解和使用,客户端可以像调用REST API一样调用这些接口。
比如 设计一个商城系统,有订单,用户,支付,商品,系统内部采用微服务架构,使用gRPC协议,具体的设计可以如下:

1. 资源导向

定义订单服务OrderService,用户服务UserService,商品服务ProductService等。

2. 使用服务方法对应CRUD

订单服务:

CreateOrder(Order)  创建订单,对应POST /orders
GetOrder(OrderId) 获取订单,对应GET /orders/{id}
UpdateOrder(Order) 更新订单,对应PUT /orders/{id}
DeleteOrder(OrderId) 删除订单,对应DELETE /orders/{id}

用户服务:

CreateUser(User) 创建用户,对应POST /users
GetUser(UserId) 获取用户,对应GET /users/{id}
UpdateUser(User) 更新用户,对应PUT /users/{id}
DeleteUser(UserId) 删除用户,对应DELETE /users/{id}

3. 统一接口格式

每个服务使用如下格式定义接口

service OrderService {
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse) {}
rpc GetOrder(GetOrderRequest) returns (GetOrderResponse) {}
// 其他方法...
}

message CreateOrderRequest {
// 请求参数
}

message CreateOrderResponse {
// 返回结果
}

4. 无状态

服务端不会储存客户端状态,每次请求必须包含所有必要信息。

5. Get接口返回全部信息

返回全部信息可以防止不同需求需要获取某个资源(比如订单)不同字段信息从而每次新定义一个接口造成接口数量急剧膨胀泛滥降低可维护性。如果某些面向客户端接口(协议)只需要特定字段,在网关层进行信息裁剪。

6.错误处理

 在RESTful API中,我们常用HTTP状态码表示错误,并在响应体中返回详细的错误信息。在gRPC接口中,我们可以返回非空的错误信息来表示错误:

// 错误信息
message Error {
int err_code = 1; //错误码
string message = 2; // 错误描述
}

// 创建订单接口
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse) {}

// 错误响应
message CreateOrderResponse {
Error error = 1; // 包含错误信息的Error消息
}

客户端调用此接口,如果返回的CreateOrderResponse中包含error信息,就表示调用失败,可以获取error消息中的详细失败原因。

7. 元数据(Labels)

 在RESTful API中,我们可以使用请求头中的元数据来提供调用相关的上下文信息。在gRPC接口中,我们可以在请求消息和响应消息中包含label字段:

message CreateOrderRequest {
// 请求参数...

// 元数据
map<string, string> labels = 10;
}

message CreateOrderResponse {
// 返回结果...

// 元数据
map<string, string> labels = 10;
}

labels是一个字符串到字符串的映射,可以包含诸如:

  • 调用者身份信息:labels[“caller”] = “xiaoming”
  • 调用目的:labels[“purpose”] = “syncorderdata”
  • 等等 服务端可以根据这些labels来进行鉴权、审计日志记录、接口调优等操作。

这样设计的RPC接口具有如下优点:

  1. 资源导向,易于理解和使用
  2. 服务方法对应CRUD操作,调用者很容易理解 
  3. 标准接口格式,各个接口看上去很规范整齐
  4. 借鉴了RESTful的理念,几乎可以当作RPC版的RESTful API来调用

所以,总体来说,这是一种模型比较清晰的RPC接口设计思路,可以让RPC的接口设计更加符合微服务架构下的实践,也更易于被人理解和使用。

本文章转载微信公众号@吃瓜技术派

#你可能也喜欢这些API文章!