如上圖所示于此同時(shí)我們還可以利用 protobuf 文件生成對(duì)應(yīng)語(yǔ)言的客戶端代碼,就不用每個(gè)項(xiàng)目都去維護(hù)一套 sdk 了,同時(shí)我們使用接口生成代碼,在 go 當(dāng)中可以使用 gomock 非常方便的對(duì)代碼進(jìn)行 mock。

API Project

使用 protobuf 定義接口可以解決我們找到 api 文檔之后,文檔不準(zhǔn)確,缺失的問(wèn)題,但是我們應(yīng)該如何找到我們的 api 呢?我們生成出的 api 文件調(diào)用方應(yīng)該如何引用呢?難道我們給每個(gè)調(diào)用方都去開一個(gè)項(xiàng)目的權(quán)限么?那明顯是不太行的,接下來(lái)我們就看看我們 api 該如何管理和組織。

毛老師他們仿照 googleapis/googleapis,istio/api 等知名項(xiàng)目在 b 站內(nèi)部搞了一個(gè) bapis 的倉(cāng)庫(kù)用于同一存放 api 定義文檔,然后通過(guò) ci/cd 生成對(duì)應(yīng)的客戶端代碼放到各個(gè)語(yǔ)言的子倉(cāng)庫(kù)當(dāng)中

工作流程如上圖所示

API Project Layout

我們的 api 項(xiàng)目是如何定義的呢?看下圖

API 設(shè)計(jì)

API 兼容性設(shè)計(jì)

隨著應(yīng)用的不斷開發(fā),業(yè)務(wù)的不斷發(fā)展我們的 api 肯定會(huì)不斷的進(jìn)行修改,在修改 api 的時(shí)候考慮 api 的兼容性就會(huì)很重要了,如果我們做了一些破壞性的變更就有可能會(huì)導(dǎo)致依賴我們的服務(wù)或者是客戶端報(bào)錯(cuò),這樣就會(huì)帶來(lái)事故。

向下兼容的變更

一般而言新增都是相對(duì)安全的,但是我們要注意的是新增字段不能改變我們?cè)镜倪壿嫞绻淖兞?api 的邏輯,那就不一定安全了

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

API 命名規(guī)范

包名

產(chǎn)品名product
應(yīng)用名app
版本號(hào)v1
包名product.app.v1
目錄結(jié)構(gòu)api/product/app/v1/xx.proto

API 定義

「標(biāo)準(zhǔn)方法」「HTTP 映射」
ListGET
GetGET
UpdatePUT 或者 PATCH
CreatePOST
DeleteDELETE

除了標(biāo)準(zhǔn)的也有一些非標(biāo)準(zhǔn)的,例如同步數(shù)據(jù)可能會(huì)用 Sync  等,不過(guò)大部分的 api 應(yīng)該都是標(biāo)準(zhǔn)的

示例

// 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"
}
};
}
}

注意,一般而言我們應(yīng)該為每個(gè)接口都創(chuàng)建一個(gè)自定義的 message,為了后面擴(kuò)展,如果我們用 Empty 的話后續(xù)就沒有辦法新增字段了

API Error

錯(cuò)誤定義

先說(shuō)我們當(dāng)前的問(wèn)題,我們一直用的 http 然后我們返回是使用的下面這種格式,然后 http code 統(tǒng)一返回 200

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

這種做法就存在一個(gè)比較大的問(wèn)題,做監(jiān)控的時(shí)候不太好做,很多現(xiàn)成的東西沒有辦法直接使用,因?yàn)槲覀兌挤祷氐某晒Α⒄?google 的錯(cuò)誤定義,將 http code 和 grpc 錯(cuò)誤碼進(jìn)行映射,返回對(duì)應(yīng)的錯(cuò)誤信息

但是這樣還是不行,因?yàn)檫@樣很多業(yè)務(wù)錯(cuò)誤信息無(wú)法區(qū)分,毛老師他們的 kratos v2 的做法是做了兩層,使用下面的方式進(jìn)行定義

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

和我們當(dāng)前的方式差不太多,但是我們是在原來(lái)的基礎(chǔ)上返回了 http code,剩下的字段還是和原來(lái)保持一致

錯(cuò)誤傳播

這一點(diǎn)我們之前做的還行,錯(cuò)誤傳播這一部分很容易出的問(wèn)題就是,當(dāng)前服務(wù)直接把上游服務(wù)的錯(cuò)誤給返回了,這樣會(huì)導(dǎo)致一些問(wèn)題:

正確的做法應(yīng)該是把上游錯(cuò)誤信息吞掉,返回當(dāng)前服務(wù)自己定義的錯(cuò)誤信息就可以了。

總結(jié)

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

參考文獻(xiàn)

  1. Go 進(jìn)階訓(xùn)練營(yíng)-極客時(shí)間
  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 設(shè)計(jì)指南  |  Google Cloud

上一篇:

為開源項(xiàng)目 go-gin-api 增加 WebSocket 模塊

下一篇:

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

我們有何不同?

API服務(wù)商零注冊(cè)

多API并行試用

數(shù)據(jù)驅(qū)動(dòng)選型,提升決策效率

查看全部API→
??

熱門場(chǎng)景實(shí)測(cè),選對(duì)API

#AI文本生成大模型API

對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)

#AI深度推理大模型API

對(duì)比大模型API的邏輯推理準(zhǔn)確性、分析深度、可視化建議合理性

10個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)