上一節(jié)講API的錯誤處理時,是java定義為例,講述了正常結(jié)果
、異常結(jié)果
、常見異常
、錯誤
四種層次的劃分,我把使用這種返回劃分的風格的API叫做:protoapi
。
protoapi
適用于同一語言、同一進程內(nèi)的API接口定義;同時也會適用于跨語言、跨進程的API接口,比方說常見的http + json的API。
在單一語言內(nèi),我們在設(shè)計類庫,提供接口的時候,會使用語言提供的接口 interface
對API做嚴格的定義。
我們使用類庫的時候,看其接口,便可以對其提供的API有準確的認知,有多少個API,每個API需要的參數(shù)是什么,返回的結(jié)果是什么,異常是什么等等,都可以準確的知道;我們調(diào)用的時候,也會有編譯器確保我們的調(diào)用API的方式是正確的。
當對于http + json的API時,API文檔往往是以網(wǎng)頁甚至word、excel的文件來提供文本描述
。
這是糟糕的做法:
- 協(xié)議不明確
- API究竟有哪些?結(jié)果的屬性有哪些?什么類型?
文本描述
是非常容易含糊其事的,特別是對于那些直接粘貼返回結(jié)果json結(jié)構(gòu)的文檔。
- API究竟有哪些?結(jié)果的屬性有哪些?什么類型?
- 難以版本化管理
- API是經(jīng)常需要修改的,如何確保當前看到的excel文件里面記錄的是最新的API?
- 調(diào)用不便
- 每個調(diào)用者都需要去自己開發(fā)一套“SDK”,重復定義文檔中的結(jié)構(gòu)是屬于重復勞動
IDL
科學的做法,應(yīng)該是引入一個IDL - Interface Definition Language
對API做嚴格的定義。
thrift、protobuf/gRPC等都是優(yōu)秀、成熟的IDL。
我曾經(jīng)使用過thrift
多年,在thrift的基礎(chǔ)上做了各種二次開發(fā),但這兩年則逐漸切換為protobuf
上,主要因為:
-
protobuf
語法提供了一定的擴展能力 - 編譯器
protoc
有很好的插件機制,二次開發(fā)更加便利
還是以登陸為例,我們使用grpc
作為為IDL的話,會是:
syntax = "proto3";
import "protoapi.proto";
package account_api;
message CommonError {
enum ErrorTypes {
UNKNOWN = 0;
INVALID_TOKEN = 1;
INVALID_PARAMETER = 2;
}
}
message LoginReq {
string username = 1 [(required) = true, (format) = "email"];
string password = 2 [(required) = true];
}
message User {
string username = 1;
int user_level = 2;
string nick = 3;
bool has_avatar = 3;
}
message AccountBalance {
uint64 amount_available = 1;
uint64 amount_due = 2;
}
message LoginResp {
User user = 1;
AccountBalance = 2;
}
message LoginError {
enum ErrorTypes {
UNKNOWN = 0;
INVALID_LOGIN = 1;
ACCOUNT_BANNED = 2;
}
ErrorTypes error = 1;
}
service Account {
option (CommonError) = FOO;
rpc login (LoginReq) returns (LoginError) {
option (BizError) = "LoginError";
}
}
HTTP + JSON
使用protobuf
作為IDL,不意味著我們一定需要使用它默認綁定的http2以及二進制的序列化。
我們使用IDL,首先是要解決文本描述
的文檔的各種問題;然后,我們可以通過自行實現(xiàn)protobuf
的插件,來實現(xiàn)包括服務(wù)器端API提供方,不同語言調(diào)用方SDK的代碼生成。
而既然代碼是我們自行生成出來,我們當然可以去實現(xiàn)使用 http + json。
具體到HTTP,protoapi
推薦使用以下風格的實現(xiàn):
URL
每個服務(wù)對應(yīng)一個API endpoint
,比方說:gateway.mydomian.TLD/api
在URL endpoint
添加PATH
,區(qū)分service以及method,比方說:
gateway.mydomian.TLD/api/Account/login
HTTP Method
- 所有API默認使用HTTP POST
- 特效場景下可以使用GET,但不鼓勵
- 因為query string無法很好的對復雜請求對象做序列化
返回結(jié)果
使用以下不同的HTTP statuscode來區(qū)分不同的返回結(jié)果:
- 正常結(jié)果
Response
:200 - 業(yè)務(wù)異常
BizError
:400 - 框架異常
CommonError
:420 - 錯誤
Error
:HTTP 500- 當HTTP返回超時,或者遇到其它的序列化、傳輸問題時,也都應(yīng)該認為是遇到錯誤
Error
- 當HTTP返回超時,或者遇到其它的序列化、傳輸問題時,也都應(yīng)該認為是遇到錯誤
最后
protoapi
所鼓勵的,是劃分結(jié)果、異常、錯誤等API風格;即便不使用任何IDL,代碼生成,只要一套API對其返回結(jié)果做了劃分,我們也可以說它是符合protoapi
協(xié)議的。
后續(xù)我會在講protoapi
的代碼生成實現(xiàn)(會開源!),以及討論protoapi
與restful
、swagger
等的關(guān)系。