> For the complete documentation index, see [llms.txt](https://meetbill.gitbook.io/butterfly-project-doc/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://meetbill.gitbook.io/butterfly-project-doc/project-framework/how/api/butterfly-rest.md).

# API 设计风格

## 1 前言

> * RPC：面向过程，也就是要做一件什么事情，只发送 GET 和 POST 请求；
>   * GET 用来查询信息，其他情况下一律用 POST；请求参数是动词。
> * RESTful：面向资源，这里的资源可以是一段文字、一个文件、一张图片，总之是一个具体的存在，可以使用 GET、POST、DELETE、PUT 请求，对应了增删查改的操作；请求参数是名词。

> 比如按照 id 查找用户：

```
 RPC 风格：GET /queryUser?userid=xxx；
 RESTful 风格：GET /user/{userid}
```

## 2 RPC 风格

RPC 通常包含传输协议和序列化协议，单说传输协议，RPC 可以建立在 TCP 协议之上（比如 Dubbo），也可以建立在 HTTP 协议之上（比如 gRPC）；如果是基于数据形式分类，RPC 又可以分成基于二进制、XML 和 JSON 三种；

对于 RPC 来讲，它通常代表 `remote procedure call`, 这里我们把它理解成为 WYGOPIAO, 也就是 what you get or post is an operation. 通过这个 RPC 类型，你通过 http 协议暴露出自己的动作以及数据封装。据我所知，通常也没有什么规则可循，但是大致是这样的：

> * endpoint 通常都是包括动作名称的
> * 通常只是使用 get 或者 post 来通信

```
GET /someoperation?data=anId

POST /anotheroperation
{
  "data":"anId";
  "anotherdata":"another value"
}
```

## 3 REST 风格

> * endpoint 通常会包含要操作的资源
> * 通常会使用 CRUD 来描述对于资源的操作

```
GET /someresources/anId

PUT /someresources/anId
{"anotherdata":"another value"}
```

> 动词 + 宾语

```
客户端发出的数据操作指令都是"动词 + 宾语"的结构。

比如，GET /articles 这个命令，GET 是动词，/articles 是宾语。
```

> 宾语必须是名词

```
/getAllCars             # 错误
/createNewCar           # 错误
/deleteAllRedCars       # 错误
/articles               # 正确
```

> 复数 URL

```
/articles/2             # 推荐
/article/2              # 不推荐
```

> 避免多级 URL, 除了第一级，其他级别都用查询字符串表达。

```
# 比如获取某个作者的某一类文章
GET /authors/12/categories/2    # 不推荐，不利于扩展，语义也不明确
GET /authors/12?categories=2    # 推荐

# 查询已发布的文章
GET /articles/published         # 不推荐
GET /articles?published=true    # 推荐
```

### 3.1 如何设计为 REST 风格

需要支持 RESTful 规范，则需要支持`动态路由`，即 `/articles/2` 形式，后面的 2 是个动态值

则可以通过如下方式实现

#### 3.1.1 约束

> 请求路径只有 1 级的 resource，且 URL 结尾不包含斜杠“/”

```
path="/resources/object/"       # 错误
path="/resources/object"        # 正确
path="/resources/"              # 错误
path="/resources"               # 正确

访问 Butterfly 时进行自动去掉末尾的 “/”
```

#### 3.1.2 分析

> 获取 resources 和 object

```
# 去掉 PATH 结尾的斜杠
path = wsgienv.get("PATH_INFO", "/")
if path.endswith("/") and path != "/":
    func_name = path[:-1].encode("ascii")
else:
    func_name = path.encode("ascii")

resource_name = ''
object_key = ''
# 当 PATH_INFO 为 "/" 时，则 index_slash 为 -1
index_slash = path.find('/', 1)
if index_slash < 0 and len(path) > 1:
    resource_name = path[1:]
elif index_slash > 1:
    resource_name = path[1:index_slash]
    object_key = path[index_slash + 1:]
self.resource_name = resource_name
self.object_key = object_key
```

> 实现 api map

```
根据 resources+mathod 自动 map 到特定的 package/func_name
```

#### 3.1.3 抛砖引玉

> handler 例子

```
class Resources(object):
    def __init__(self, req, object_key=None)
        ...
    def list(self, arg1=xx):
        ...
    def get(self, arg1=xx):
        ...
    def create(self):
        ...
    def update(self):
        ...
    def delete(self):
        ...

---------------------------------------------------------------------------
get /app/resources/object  ===> 将 object_key 进行初始化，并调用 get 方法
get /app/resources         ===> object_key 传 None, 调用 list 方法
post /app/resources/object ===> 将 object_key 进行初始化，并调用 create 方法
等等
```

## 4 传送门

> * [restless](https://github.com/toastdriven/restless)
> * [Google Cloud API 设计指南](https://cloud.google.com/apis/design?hl=zh-cn)
> * [RESTful API 最佳实践 -- 阮一峰](http://www.ruanyifeng.com/blog/2018/10/restful-api-best-practices.html)
> * [API 设计的几条原则](https://insights.thoughtworks.cn/how-to-design-api/)
> * <https://github.com/Jaymon/endpoints> rest 风格 api
