如上圖所示于此同時我們還可以利用 protobuf 文件生成對應語言的客戶端代碼,就不用每個項目都去維護一套 sdk 了,同時我們使用接口生成代碼,在 go 當中可以使用 gomock 非常方便的對代碼進行 mock。

API Project

使用 protobuf 定義接口可以解決我們找到 api 文檔之后,文檔不準確,缺失的問題,但是我們應該如何找到我們的 api 呢?我們生成出的 api 文件調用方應該如何引用呢?難道我們給每個調用方都去開一個項目的權限么?那明顯是不太行的,接下來我們就看看我們 api 該如何管理和組織。

毛老師他們仿照 googleapis/googleapis,istio/api 等知名項目在 b 站內部搞了一個 bapis 的倉庫用于同一存放 api 定義文檔,然后通過 ci/cd 生成對應的客戶端代碼放到各個語言的子倉庫當中

工作流程如上圖所示

API Project Layout

我們的 api 項目是如何定義的呢?看下圖

API 設計

API 兼容性設計

隨著應用的不斷開發,業務的不斷發展我們的 api 肯定會不斷的進行修改,在修改 api 的時候考慮 api 的兼容性就會很重要了,如果我們做了一些破壞性的變更就有可能會導致依賴我們的服務或者是客戶端報錯,這樣就會帶來事故。

向下兼容的變更

一般而言新增都是相對安全的,但是我們要注意的是新增字段不能改變我們原本的邏輯,如果改變了 api 的邏輯,那就不一定安全了

向下不兼容的變更(破壞性變更)

API 命名規范

包名

產品名product
應用名app
版本號v1
包名product.app.v1
目錄結構api/product/app/v1/xx.proto

API 定義

「標準方法」「HTTP 映射」
ListGET
GetGET
UpdatePUT 或者 PATCH
CreatePOST
DeleteDELETE

除了標準的也有一些非標準的,例如同步數據可能會用 Sync  等,不過大部分的 api 應該都是標準的

示例

// api/product/app/v1/blog.proto

syntax = "proto3";

package product.app.v1;

import "google/api/annotations.proto";

// blog service is a blog demo
service BlogService {

rpc GetArticles(GetArticlesReq) returns (GetArticlesResp) {
option (google.api.http) = {
get: "/v1/articles"
additional_bindings {
get: "/v1/author/{author_id}/articles"
}
};
}
}

注意,一般而言我們應該為每個接口都創建一個自定義的 message,為了后面擴展,如果我們用 Empty 的話后續就沒有辦法新增字段了

API Error

錯誤定義

先說我們當前的問題,我們一直用的 http 然后我們返回是使用的下面這種格式,然后 http code 統一返回 200

{
"code": 1,
"msg": "xxx",
"data": {}
}

這種做法就存在一個比較大的問題,做監控的時候不太好做,很多現成的東西沒有辦法直接使用,因為我們都返回的成功。參照 google 的錯誤定義,將 http code 和 grpc 錯誤碼進行映射,返回對應的錯誤信息

但是這樣還是不行,因為這樣很多業務錯誤信息無法區分,毛老師他們的 kratos v2 的做法是做了兩層,使用下面的方式進行定義

message Status {
// 錯誤碼,跟 grpc-status 一致,并且在HTTP中可映射成 http-status
int32 code = 1;
// 錯誤原因,定義為業務判定錯誤碼
string reason = 2;
// 錯誤信息,為用戶可讀的信息,可作為用戶提示內容
string message = 3;
// 錯誤詳細信息,可以附加自定義的信息列表
repeated google.protobuf.Any details = 4;
}

和我們當前的方式差不太多,但是我們是在原來的基礎上返回了 http code,剩下的字段還是和原來保持一致

錯誤傳播

這一點我們之前做的還行,錯誤傳播這一部分很容易出的問題就是,當前服務直接把上游服務的錯誤給返回了,這樣會導致一些問題:

正確的做法應該是把上游錯誤信息吞掉,返回當前服務自己定義的錯誤信息就可以了。

總結

毛老師課上講的 api 設計思路用起來還是挺爽的,我們已經在一個項目當中進行了試點,cicd 的流程也跑了起來,最爽的一點就是終于不用找接口文檔了,然后還節省了一些代碼量,我們之前的接口調用方式都是十分原始的,每個項目都自己去封裝相關的 sdk 然后我們對單元測試還有要求,http 接口的 mock 是挺麻煩的事情,通過 protobuf 定義接口之后我寫了一個結合內部網關的 sdk 代碼生成器,直接生成相關接口代碼,go interface 的 mock 實現也在 ci 流程中生產好了,調用方只需要調用不同的實現就行了。下一篇我們就通過寫一個 從 proto 生成 gin 代碼的生成器來看看這個代碼生成器改如何實現。

參考文獻

  1. Go 進階訓練營-極客時間
  2. GitHub – istio/api: API definitions for the Istio project
  3. GitHub – envoyproxy/data-plane-api: [READ ONLY MIRROR] Envoy REST/proto API definitions and documentation.
  4. GitHub – googleapis/googleapis: Public interface definitions of Google APIs.
  5. API 設計指南  |  Google Cloud

上一篇:

為開源項目 go-gin-api 增加 WebSocket 模塊

下一篇:

Go工程化(五) API 設計下: 基于 protobuf 自動生成 gin 代碼
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費