Golang 中的 RESTful API 最佳實踐

【導讀】本文結合 Go Web 再次梳理了 RESTful API 的相關最佳實踐。

RESRful API 已經流行很多年了,我也一直在使用它。最佳實踐也看過不少,但當一個項目完成,再次回顧 / 梳理項目時,會發現很多 API 和規範還是多少有些出入。在這篇文章中,我們結合 Go Web 再次梳理一下 RESTful API 的相關最佳實踐。

示例完整代碼在這裏 https://github.com/razeencheng/demo-go/blob/master/restful-api/main.go

1. 使用 JSON

不管是接收還是返回數據都推薦使用 JSON。

通常返回數據的格式有 JSON 和 XML,但 XML 過於冗長,可讀性差,而且各種語言的解析上也不如 JSON,使用 JSON 的好處,顯而易見。

而接收數據,我們這裏也推薦使用 JSON,對於後端開發而言,入參直接與模型綁定,省去冗長的參數解析能簡化不少代碼,而且 JSON 能更簡單的傳遞一些更復雜的結構等。

正如示例代碼中的這一段,我們以gin框架爲例。

// HandleLogin doc
func HandleLogin(c *gin.Context) {
    param := &LoginParams{}
    if err := c.BindJSON(param); err != nil {
        c.JSON(http.StatusBadRequest, &Resp{Error: "parameters error"})
        return
    }

    // 做一些校驗
    // ...

    session := sessions.Default(c)
    session.Set(sessionsKey, param.UserID)
    session.Save()
    c.JSON(http.StatusOK, &Resp{Data: "login succeed"})
}

通過c.BindJSON, 輕鬆的將入參於模型LoginParams綁定;通過c.JSON輕鬆的將數據 JSON 序列化返回。

但所有接口都必須用 JSON 麼?那也未必。比如文件上傳,這時我們使用FormData比把文件 base64 之類的放到 JSON 裏面更高效。

2. 路徑中不包含動詞

我們的 HTTP 請求方法中已經有GET,POST等這些動作了,完全沒有必要再路徑中加上動詞。

我們常用 HTTP 請求方法包括GET,POST,PUTDELETE, 這也對應了我們經常需要做的數據庫操作。GET查找 / 獲取資源,POST新增資源,PUT修改資源,DELETE刪除資源。

如下,這些路徑中沒有任何動詞,簡潔明瞭。

// 獲取文章列表
v1.GET("/articles", HandleGetArticles)
// 發佈文章
v1.POST("/articles", HandlePostArticles)
// 修改文章
v1.PUT("/articles", HandleUpdateArticles)
// 刪除文章
v1.DELETE("/articles/:id", HandleDeleteArticles)

3. 路徑中對應資源用複數

就像我們上面這段代碼,articles對於的是我們的文章資源,背後就是一張數據庫表articles, 所以操作這個資源的應該都用複數形式。

4. 次要資源可分層展示

一個博客系統中,最主要的應該是文章了,而評論應該是其子資源,我們可以評論嵌套在它的父資源後面,如:

// 獲取評論列表
v1.GET("/articles/:articles_id/comments", HandleGetComments)
// 添加評論
v1.POST("/articles/:articles_id/comments", HandleAddComments)
// 修改評論
v1.PUT("/articles/:articles_id/comments/:id", HandleUpdateComments)
// 刪除評論
v1.DELETE("/articles/:articles_id/comments/:id", HandleDeleteComments)

那麼,我們需要獲取所有文章的評論怎麼辦?可以這麼寫:

v1.GET("/articles/-/comments", HandleGetComments)

但這也不是決對的,資源雖然有層級關係,但這種層級關係不宜太深,個人感覺兩層最多了,如果超過,可以直接拿出來放在一級。

5. 分頁、排序、過濾

獲取列表時,會使用到分頁、排序過濾。一般:

?page=1&page_size=10  # 指定頁面page與分頁大小page_size
?sort=-create_at,+author # 按照創建時間create_at降序,作者author升序排序
?title=helloworld # 按字段title搜索

6. 統一數據格式

不管是路徑的格式,還是參數的格式,還是返回值的格式建議統一形式。

一般常用的格式有蛇形,大駝峯小駝峯,個人比較喜歡蛇形。Anyway, 不管哪種,只要統一即可。

除了參數的命名統一外,返回的數據格式,最好統一,方便前端對接。

如下,我們定義Resp爲通用返回數據結構,Data中存放反會的數據,如果出錯,將錯誤信息放在Error中。

// Resp doc
type Resp struct {
    Data  interface{} `json:"data"`
    Error string      `json:"error"`
}

// 登陸成功返回
  c.JSON(http.StatusOK, &Resp{Data: "login succeed"})
// 查詢列表
    c.JSON(http.StatusOK, &Resp{Data: map[string]interface{}{
        "result": tempStorage,
        "total":  len(tempStorage),
    }})
// 參數錯誤
    c.JSON(http.StatusBadRequest, &Resp{Error: "parameters error"})

7. 善用 HTTP 狀態碼

HTTP 狀態碼有很多,我們沒有必要也不可能全部用上,常用如下:

其中502,503,我們寫程序時並不會明確去拋出。所以我們平常用 6 個狀態碼已經能很好的展示服務端狀態了。

同時,我們將狀態與返回值對應起來,200狀態下,返回Data數據;其他狀態返回Error

8.API 版本化

正如 Demo 中所示,我們將路由分組到了/api/v1路徑下面,版本化 API。如果後續的服務端升級,但可能仍有很大部分客戶端請求未升級,依然請求老版本的 API,那麼我們只需要增加/api/v2,然後在該路徑下爲已升級的客戶端提供服務。這樣,我們就做到了 API 的版本控制,可以平滑的從一個版本切換到另外一個版本。

    v1 := r.Group("/api/v1")
    {
        v1.POST("/login", HandleLogin)
        v1.GET("/articles", HandleGetArticles)
        v1.GET("/articles/:id/comments", HandleGetComments)
    // ....

9. 統一 / 開頭

所以路由中,路徑都以/開頭,雖然框架會爲我們做這件事,但還是建議統一加上。

10. 增加 / 更新操作 返回資源

對於POST,PUT操作,建議操作後,返回更新後的資源。

11. 使用 HTTPS

對於暴露出去的接口/OpenAPI,一定使用 HTTPS。一般時候,我們可以直接在服務前面架設一個 WebServer,在 WebServer 內部署證書即可。當然,如果是直接由後端暴露出的接口,有必要直接在後端開啓 HTTPS!

12. 規範的 API 文檔

對於我們這種前後端分離的架構,API 文檔是很重要。在 Go 中,我們很容易的能用 swag 結合代碼註釋自動生成 API 文檔。

總結

API 寫的好不好,重要的還是看是否遵循 WEB 標準和保持一致性,最終目的也是讓這些 API 更清晰,易懂,安全,希望這些建議對你有所幫助。

轉自:

razeencheng.com/posts/golang-and-restful-api/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/jUBIPf5kh-giNdeyjP1sKg