# RESTful API
一般用于处理对外业务。
# URI
- 资源名使用名词复数表示。资源分为 Collection 和 Member 两种。
- Collection:一堆资源的集合。比如系统里有很多用户(User), 这些用户的集合就是 Collection。Collection 的 URI 标识应该是 域名/资源名复数, 比如 https:// iam.api.marmotedu.com/users。
- Member:单个特定资源。比如系统中特定名字的用户,就是 Collection 里的一个 Member。Member 的 URI 标识应该是 域名/资源名复数/资源名称, 比如 https:// iam.api.marmotedu/users/admin。URI 结尾不应包含/。
- URI 中不能出现下划线 _,可用中杠线 - 代替(统一一种即可)。
- URI 路径用小写。
- 避免层级过深的 URI。超过 2 层的资源嵌套较乱,建议将其他资源转化为 ? 参数,比如:
/students?school=qinghua&class=rooma
以 Query 参数代替/schools/tsinghua/classes/rooma/students/zhang
Path 参数。
当某些操作不便于映射为某个 REST 资源:
- 将操作变成资源属性,比如暂时禁用某个用户:
/users/zhangsan?active=false
。 - 将操作当作是资源的嵌套资源,比如 GitHub 加星:
PUT /gists/:id/star
。 - 以上都不能解决问题,则可以打破规范。比如登录操作
/login
。
# HTTP 方法
常用 GET(获取)、PUT(替换)、POST(新增)、DELETE(删除),其中 PUT 表示替换资源,不建议使用。
注意 安全性(不会改变资源状态,GET)和 幂等性(执行多次效果等价,GET、PUT、DELETE)。
关于批量删除,建议在操作路径中带多个用分隔符分隔的 id,比如:DELETE /users?ids=1,2,3
。可避免发送多次请求(可根据实际情况选择实现)。
# 返回路径
RESTful API 会向外界开放多个资源的接口,返回格式要保持一致;每个接口都会返回成功和失败两种消息,格式也要保持一致。
否则客户端代码要适配不同接口的返回格式,每个返回格式又要适配成功和失败两种消息格式,增加用户的学习和使用成本。
返回格式没有强制标准,可根据实际业务需要返回不同格式。
# 错误码
错误码需要附带业务标识,且对内对外分别展示不同的错误信息(不必对外暴露内部信息)。
建议在 HTTP Status Code 根据错误类型设置,并在 Body 附带详细的的错误信息(简明扼要,统一大写开头,不带 .
)。
业务码可覆盖场景:基本错误、数据库类错误、认证授权类错误、加解码类错误。参考 iam/error_code_generated.md (opens new window),示例:100101
- 10:服务代号。
- 01:功能模块代号。
- 01:功能模块下错误码序号。
HTTP Status Code 参考 HTTP 协议规范即可,一般以下几个即可满足需求:
- 200:请求成功执行。
- 400:客户端错误。
- 401:认证失败。
- 403:授权失败。
- 404:资源找不到,可以是 URL 或 RESTful 资源。
- 500:服务端错误。
可使用自定义的错误包以支持业务错误码,参考 marmotedu/errors (opens new window)。
# API 版本
通常将版本标识放置在:
- URL 中,比如
/v1/users
。最直观,GitHub、Kubernetes、Etcd 都采用的做法。 - HTTP Header 中,比如
Accept: vnd.example-com.foo+json; version=1.0
。 - Form 参数中,比如
/users?version=v1
。
# API 命名
驼峰命名法(serverAddress)和蛇形命名法(server_address)需要切换输入法,会增加操作的复杂性,也容易出错。
建议用 脊柱命名法(server-address),参考 GitHub API。
# 分页 / 过滤 / 排序 / 搜索
- 分页:比如
/users?offset=0&limit=20
,可减少 API 响应延时、避免返回太多条目,导致服务器 / 客户端响应慢,甚至导致服务器 / 客户端 crash。 - 过滤:如果不需要资源全部状态属性,可在 URI 参数里指定返回的属性,比如
/users?fields=email,username,address
。 - 排序:可在 URI 参数中指明排序参数,比如
/users?sort=age,desc
。 - 搜索:建议按模糊匹配来搜索。
# 域名
建议采用 iam.api.marmotedu.com,支持 marmotedu.com 域名下扩展另一套域名系统,比如:storage.api.marmotedu.com、network.api.marmotedu.com。
# RPC API
一般用于处理对内业务。
# gRPC
具备以下特点:
- 调用方便:屏蔽了底层的网络通信细节、调用方便:
ClassName.ClassFuc(params)
。 - 无需打包和解包:RPC 调用的入参和返回的结果都是 Go 结构体,简化了调用步骤。
支持 4 种服务方法:
- 简单模式(Simple RPC):客户端发送一次请求,服务端响应一次数据,
rpc SayHello (HelloRequest) returns (HelloReply) {}
。 - 服务端数据流模式:客户端发送请求,服务器返回数据流响应,客户端从流中读取数据直到为空,
rpc SayHello (HelloRequest) returns (stream HelloReply) {}
。 - 客户端数据流模式:客户端将消息流发送给服务器,服务器全部处理完成后返回一次响应,
rpc SayHello (stream HelloRequest) returns (HelloReply) {}
。 - 双向数据流模式:客户端和服务端都可同时相互发送数据流,
rpc SayHello (stream HelloRequest) returns (stream HelloReply) {}
。
与 RESTful API 的对比:
# SDK
即由服务提供者或其它组织或个人提供的、封装后端服务 API 的软件包,通常包含相关的库、文档、使用示例、封装好的 API 接口和工具。
命名方式:xxx-sdk-go / xxx-sdk-python / xxx-sdk-java 等。
目录结构:
- README.md:帮助文档,包含了安装、配置和使用 SDK 的方法。
- examples/sample/:使用示例。
- sdk/:SDK 共享包,封装最基础的通信功能。如果是 HTTP 服务,基本是基于 net/http 包封装。
- api:如果 xxx-sdk-go 只为某一个服务提供 SDK,可以把该服务的所有 API 封装代码存放在 api 目录下。
- services/{iam, tms} :如果 xxx-sdk-go 中 xxx 是一个组织,这个 SDK 很可能会集成该组织中很多服务的 API,可以把某类服务 API 封装代码存放在 services/<服务名>下,如 AWS 的 Go SDK。
├── examples # 示例代码存放目录
│ └── authz.go
├── README.md # SDK使用文档
├── sdk # 公共包,封装了SDK配置、API请求、认证等代码
│ ├── client.go
│ ├── config.go
│ ├── credential.go
│ └── ...
└── services # API封装
├── common
│ └── model
├── iam # iam服务的API接口
│ ├── authz.go
│ ├── client.go
│ └── ...
└── tms # tms服务的API接口
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
├── examples # SDK 的使用示例
├── Makefile # 管理 SDK 源码,静态代码检查、代码格式化、测试、添加版权信息等
├── marmotedu
│ ├── clientset.go # clientset 实现,包含多个应用,多个服务的API接口
│ ├── fake # clientset 的 fake 实现,用于单元测试
│ └── service # 按应用进行分类,存放应用中各服务 API 接口的具体实现
│ ├── iam # iam 应用的 API 接口实现,包含多个服务
│ │ ├── apiserver # iam 应用中,apiserver 服务的 API 接口,包含多个版本
│ │ │ └── v1 # apiserver v1 版本 API 接口
│ │ ├── authz # iam 应用中,authz 服务的 API 接口
│ │ │ └── v1 # authz 服务 v1 版本接口
│ │ └── iam_client.go # iam 应用的客户端,包含了 apiserver 和 authz 2 个服务的客户端
│ └── tms # tms 应用的 API 接口实现
├── pkg # 存放一些共享包,可对外暴露
├── rest # HTTP 请求的底层实现
├── third_party # 存放修改过的第三方包,例如:gorequest
└── tools
└── clientcmd # 用来帮助创建 rest.Config 配置的函数
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 设计思路
通过 Config
配置创建客户端 Client,例如 func NewClient(config sdk.Config) (Client, error)
,在配置中可以指定:
- 服务的后端地址:服务的后端地址可以通过配置文件来配置,也可以直接固化在 SDK 中,推荐后端服务地址可通过配置文件配置。
- 认证信息:最常用的认证方式是通过密钥认证,也有一些是通过用户名和密码认证。
- 其他配置:例如超时时间、重试次数、缓存时间等。
创建的 Client 是 struct 或 interface。建议使用 interface,可以将定义和具体实现解耦。Client 的每个方法对应一个 API,比如:
type Client struct {
client *sdk.Request
}
// req 中可指定 HTTP 请求方法、路径、消息体。
func (c *Client) CreateUser(req *CreateUserRequest) (*CreateUserResponse, error) {
// normal code
resp := &CreateUserResponse{}
// 发起 HTTP 请求:c.client 是 *Request 类型。
// 可根据传入的请求参数 req 和 config 配置构造出请求路径、认证头和请求 Body,
// 并调用 net/http 包完成最终的 HTTP 请求,将返回结果 Unmarshal 到传入的 resp 结构体中。
err := c.client.Send(req, resp)
return resp, err
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 公有云厂商的 SDK 设计
参考 marmotedu/medu-sdk-go (opens new window)。
- API 层构建客户端实例,并调用客户端实例提供的方法来完成 API 请求,每个方法对应一个 API。API 层最终会调用基础层提供的能力来完成 REST API 请求。
- 基础层通过依次执行构建请求参数(Builder)、签发并添加认证头(Signer)、执行 HTTP 请求(Request)三大步骤完成具体的 REST API 请求。
# client-go 风格的 SDK 设计
参考 marmotedu/marmotedu-sdk-go (opens new window)。
具备以下特点:
- 大量使用 Go interface 特性,将接口的定义和实现解耦,支持多种实现方式。
- 接口调用层级与资源的层级相匹配,调用方式更友好。
- 支持多版本共存。
# 命令行工具
大型项目中配备命令行工具(比如 kubectl、istioctl、etcdctl),可通过在脚本中调用 实现自动化,且通过将应用的功能封装成命令和参数,方便运维、开发人员在 Linux 服务器上调用。
待续。