上一節(jié)講API的錯(cuò)誤處理時(shí),是java定義為例胰伍,講述了正常結(jié)果
盟戏、異常結(jié)果
、常見(jiàn)異常
庆寺、錯(cuò)誤
四種層次的劃分,我把使用這種返回劃分的風(fēng)格的API叫做:protoapi
诉字。
protoapi
適用于同一語(yǔ)言懦尝、同一進(jìn)程內(nèi)的API接口定義;同時(shí)也會(huì)適用于跨語(yǔ)言壤圃、跨進(jìn)程的API接口陵霉,比方說(shuō)常見(jiàn)的http + json的API。
在單一語(yǔ)言?xún)?nèi)伍绳,我們?cè)谠O(shè)計(jì)類(lèi)庫(kù)踊挠,提供接口的時(shí)候,會(huì)使用語(yǔ)言提供的接口 interface
對(duì)API做嚴(yán)格的定義冲杀。
我們使用類(lèi)庫(kù)的時(shí)候效床,看其接口,便可以對(duì)其提供的API有準(zhǔn)確的認(rèn)知权谁,有多少個(gè)API剩檀,每個(gè)API需要的參數(shù)是什么,返回的結(jié)果是什么旺芽,異常是什么等等沪猴,都可以準(zhǔn)確的知道;我們調(diào)用的時(shí)候采章,也會(huì)有編譯器確保我們的調(diào)用API的方式是正確的运嗜。
當(dāng)對(duì)于http + json的API時(shí),API文檔往往是以網(wǎng)頁(yè)甚至word共缕、excel的文件來(lái)提供文本描述
洗出。
這是糟糕的做法:
- 協(xié)議不明確
- API究竟有哪些?結(jié)果的屬性有哪些图谷?什么類(lèi)型翩活?
文本描述
是非常容易含糊其事的阱洪,特別是對(duì)于那些直接粘貼返回結(jié)果json結(jié)構(gòu)的文檔。
- API究竟有哪些?結(jié)果的屬性有哪些图谷?什么類(lèi)型翩活?
- 難以版本化管理
- API是經(jīng)常需要修改的菠镇,如何確保當(dāng)前看到的excel文件里面記錄的是最新的API冗荸?
- 調(diào)用不便
- 每個(gè)調(diào)用者都需要去自己開(kāi)發(fā)一套“SDK”,重復(fù)定義文檔中的結(jié)構(gòu)是屬于重復(fù)勞動(dòng)
IDL
科學(xué)的做法利耍,應(yīng)該是引入一個(gè)IDL - Interface Definition Language
對(duì)API做嚴(yán)格的定義蚌本。
thrift、protobuf/gRPC等都是優(yōu)秀隘梨、成熟的IDL程癌。
我曾經(jīng)使用過(guò)thrift
多年,在thrift的基礎(chǔ)上做了各種二次開(kāi)發(fā)轴猎,但這兩年則逐漸切換為protobuf
上嵌莉,主要因?yàn)椋?/p>
-
protobuf
語(yǔ)法提供了一定的擴(kuò)展能力 - 編譯器
protoc
有很好的插件機(jī)制,二次開(kāi)發(fā)更加便利
還是以登陸為例捻脖,我們使用grpc
作為為IDL的話锐峭,會(huì)是:
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,不意味著我們一定需要使用它默認(rèn)綁定的http2以及二進(jìn)制的序列化可婶。
我們使用IDL沿癞,首先是要解決文本描述
的文檔的各種問(wèn)題;然后矛渴,我們可以通過(guò)自行實(shí)現(xiàn)protobuf
的插件椎扬,來(lái)實(shí)現(xiàn)包括服務(wù)器端API提供方,不同語(yǔ)言調(diào)用方SDK的代碼生成曙旭。
而既然代碼是我們自行生成出來(lái)盗舰,我們當(dāng)然可以去實(shí)現(xiàn)使用 http + json晶府。
具體到HTTP桂躏,protoapi
推薦使用以下風(fēng)格的實(shí)現(xiàn):
URL
每個(gè)服務(wù)對(duì)應(yīng)一個(gè)API endpoint
,比方說(shuō):gateway.mydomian.TLD/api
在URL endpoint
添加PATH
川陆,區(qū)分service以及method剂习,比方說(shuō):
gateway.mydomian.TLD/api/Account/login
HTTP Method
- 所有API默認(rèn)使用HTTP POST
- 特效場(chǎng)景下可以使用GET,但不鼓勵(lì)
- 因?yàn)閝uery string無(wú)法很好的對(duì)復(fù)雜請(qǐng)求對(duì)象做序列化
返回結(jié)果
使用以下不同的HTTP statuscode來(lái)區(qū)分不同的返回結(jié)果:
- 正常結(jié)果
Response
:200 - 業(yè)務(wù)異常
BizError
:400 - 框架異常
CommonError
:420 - 錯(cuò)誤
Error
:HTTP 500- 當(dāng)HTTP返回超時(shí)较沪,或者遇到其它的序列化鳞绕、傳輸問(wèn)題時(shí),也都應(yīng)該認(rèn)為是遇到錯(cuò)誤
Error
- 當(dāng)HTTP返回超時(shí)较沪,或者遇到其它的序列化鳞绕、傳輸問(wèn)題時(shí),也都應(yīng)該認(rèn)為是遇到錯(cuò)誤
最后
protoapi
所鼓勵(lì)的尸曼,是劃分結(jié)果们何、異常、錯(cuò)誤等API風(fēng)格控轿;即便不使用任何IDL冤竹,代碼生成拂封,只要一套API對(duì)其返回結(jié)果做了劃分,我們也可以說(shuō)它是符合protoapi
協(xié)議的鹦蠕。
后續(xù)我會(huì)在講protoapi
的代碼生成實(shí)現(xiàn)(會(huì)開(kāi)源冒签!),以及討論protoapi
與restful
钟病、swagger
等的關(guān)系萧恕。