Go 工程化 -四- API 設計上: 項目結構 - 設計
API 項目結構與管理
API 定義方式
b 站內部主要使用 grpc 作爲內部通信的方式,因爲他使用 protobuf 文件定義可以支持對語言代碼生成,同時還避免了手寫文檔導致的文檔錯誤過時等情況,具體的原因其實在第一課的筆記當中就有提到,如果感興趣可以查看 微服務 (二) 服務發現 & 多租戶 #gRPC
我們目前使用類似 http restful 的方式進行對外對內提供服務,但是我們之前的 API 管理其實是比較混亂的,分爲以下幾種情況:
-
暴露給 web 的 api:有使用 swagger 的,有在文檔平臺上寫文檔的,還有沒有寫文檔的
-
暴露給其他服務調用的 api: 有註冊到內部的接口網關的,但是內部的接口網關上有的有參數,有的沒有,沒有返回值定義
所以就存在很多問題:
-
想要接口不知道從哪兒找,只能到處問人
-
有時候從內部網關平臺上找到接口但是不知道怎麼調用,沒有寫任何參數,有的寫了還有可能是錯的
-
有的壓根沒有接口文檔,對接的同學也沒有時間寫,然後讓你直接看代碼
-
有的對接同學扔給你一個接口文檔,然後試了半天發現,有問題,溝通排查之後發現文檔很久沒有更新了 o(╥﹏╥)o
所以課程上毛老師提到的利用 protobuf 來定義接口的方式非常令人心動,因爲 protobuf 當中包含了接口的函數簽名,入參和返回值同時還支持註釋,就是一份天然的文檔,同時也不用擔心出現代碼更新了但是文檔沒有更新的情況,因爲它既是文檔也是代碼,服務端也需要使用,所以代碼更新之後文檔也一定會更新。自然而然的就少了很多溝通的成本。
API Project
使用 protobuf 定義接口可以解決我們找到 api 文檔之後,文檔不準確,缺失的問題,但是我們應該如何找到我們的 api 呢?我們生成出的 api 文件調用方應該如何引用呢?難道我們給每個調用方都去開一個項目的權限麼?那明顯是不太行的,接下來我們就看看我們 api 該如何管理和組織。
毛老師他們仿照 googleapis/googleapis,istio/api 等知名項目在 b 站內部搞了一個 bapis 的倉庫用於同一存放 api 定義文檔,然後通過 ci/cd 生成對應的客戶端代碼放到各個語言的子倉庫當中
-
開發同學修改了 proto 文件定義之後 push 到對應的業務應用倉庫當中
-
然後觸發 cicd 流程將 proto 文件複製到 api project 當中
-
首先會對 proto 文件進行靜態代碼分析,查看是否符合規範
-
然後 clone api project 創建一個新的分支
-
然後 push 代碼,創建一個 merge request 請求
-
然後我們對應負責的同學收到 code review 的通知之後進行 code review,沒有問題就會合併到 api project 的主分支當中了
-
然後就會觸發 cicd 生成對應語言的客戶端代碼,push 到對應的各個子倉庫當中了
API Project Layout
我們的 api 項目是如何定義的呢?看下圖
-
首先是在業務項目當中,我們頂層會有一個 api 目錄
-
在 api 目錄當中我們會按照 product name/app name / 版本號 / app.proto 的方式進行組織
-
具體怎麼組織可能每個公司都不太一樣,但是總的來說就是應用的 唯一名稱 + 版本號 來進行一個區分
-
在 api project 當中和業務應用類似,也有一個 api 目錄,通過上圖的兩個框就可以發現這是一模一樣的
-
除此之外 api project 還有用於註解的 annotations 文件夾
-
有一些第三方的引用,例如 googleapis 當中的一些 proto 文件
API 設計
API 兼容性設計
隨着應用的不斷開發,業務的不斷髮展我們的 api 肯定會不斷的進行修改,在修改 api 的時候考慮 api 的兼容性就會很重要了,如果我們做了一些破壞性的變更就有可能會導致依賴我們的服務或者是客戶端報錯,這樣就會帶來事故。
向下兼容的變更
-
新增接口
-
新增參數字段
-
新增返回字段
-
在不改變其他響應字段的行爲的前提下,非資源(例如,ListBooksResponse)的響應消息可以擴展而不必破壞客戶端的兼容性。即使會引入冗餘,先前在響應中填充的任何字段應繼續使用相同的語義填充。
一般而言新增都是相對安全的,但是我們要注意的是新增字段不能改變我們原本的邏輯,如果改變了 api 的邏輯,那就不一定安全了
向下不兼容的變更(破壞性變更)
-
刪除或重命名服務,字段,方法或枚舉值
-
在做這種修改的時候需要修改我們 api 的版本號,常見有兩種方式
-
如果只有很少的 api 變動可以創建一個 XXXV2 的方法
-
如果變動的 api 比較多,可以直接新啓一個 v2 的包
-
修改字段的類型
-
嚴禁修改字段的類型,修改字段的類型可能會導致客戶端崩潰
-
修改現有請求的可見行爲
-
給資源消息添加 讀取 / 寫入 字段
API 命名規範
包名
| 產品名 | product | | --- | --- | | 應用名 | app | | 版本號 | v1 | | 包名 | product.app.v1 | | 目錄結構 | api/product/app/v1/xx.proto |
API 定義
-
命名規則:方法 + 資源
-
標準方法:參考 Google API 設計指南
| 「標準方法」 | 「HTTP 映射」 | | --- | --- | | List | GET | | Get | GET | | Update | PUT 或者 PATCH | | Create | POST | | Delete | DELETE |
除了標準的也有一些非標準的,例如同步數據可能會用 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 錯誤碼進行映射,返回對應的錯誤信息
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 代碼的生成器來看看這個代碼生成器改如何實現。
參考文獻
-
Go 進階訓練營 - 極客時間
-
GitHub - istio/api: API definitions for the Istio project
-
GitHub - envoyproxy/data-plane-api: [READ ONLY MIRROR] Envoy REST/proto API definitions and documentation.
-
GitHub - googleapis/googleapis: Public interface definitions of Google APIs.
-
API 設計指南 | Google Cloud
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/cXLx_BpFg0gNQUK8xpHQJg