完善的 API 的 4 個(gè)基本特征
創(chuàng)建一個(gè) API(應(yīng)用程序接口),我們所要做的遠(yuǎn)遠(yuǎn)不止是讓它能“正常工作”。
如果你正在構(gòu)建基于 C/S 模型的應(yīng)用程序,那么你需要一個(gè)應(yīng)用程序接口(API)。API 就是一種非常清晰而又明確的定義,它是一個(gè)進(jìn)程與另一個(gè)進(jìn)程之間明確定義的邊界。Web 應(yīng)用中我們常見(jiàn)的邊界定義就是 REST/JSON API。
雖然很多開(kāi)發(fā)者可能主要關(guān)注在如何讓 API 正常工作(或功能正常),但卻還有一些“非功能性”的要求也是需要他們注意的。所有的 API 必須具備 的 4 個(gè)非功能性的要求是:
- 安全
- 文檔
- 驗(yàn)證
- 測(cè)試
安全
在軟件開(kāi)發(fā)中,安全是最基本的要求。對(duì)于 API 開(kāi)發(fā)者來(lái)說(shuō),API 的安全性主要包含以下 4 個(gè)方面:
- HTTPS/SSL 證書(shū)
- 跨域資源共享
- 身份認(rèn)證與 JSON Web 令牌
- 授權(quán)與作用域
1、HTTPS/SSL 證書(shū)
Web 應(yīng)用的黃金標(biāo)準(zhǔn)是使用 SSL 證書(shū)的 HTTPS 協(xié)議。Let's Encrypt 可以幫你達(dá)到這一目的。Let's Encrypt 來(lái)自于非營(yíng)利性的互聯(lián)網(wǎng)安全研究小組(ISRG),它是一個(gè)免費(fèi)的、自動(dòng)化的、開(kāi)放的證書(shū)頒發(fā)機(jī)構(gòu)。
Let's Encrypt 的軟件會(huì)為你的域(LCTT 譯注:包含域名、IP 等信息)生成中央授權(quán)證書(shū)。而正是這些證書(shū)確保了從你的 API 到客戶(hù)端的數(shù)據(jù)載荷是點(diǎn)對(duì)點(diǎn)加密的。
Let's Encrypt 支持證書(shū)管理的多種部署方案。我們可以通過(guò)查看 文檔 來(lái)找出最適合自己需要的方案。
2、跨域資源共享
跨域資源共享(CORS)是一個(gè)針對(duì)瀏覽器的安全策略預(yù)檢。如果你的 API 服務(wù)器與發(fā)出請(qǐng)求的客戶(hù)端不在同一個(gè)域中,那么你就要處理 CORS。例如,如果你的服務(wù)器運(yùn)行在 api.domain-a.com
并且接到一個(gè)來(lái)自 domain-b.com
的客戶(hù)端的請(qǐng)求,那么 CORS 就會(huì)(讓瀏覽器)發(fā)送一個(gè) HTTP 預(yù)檢請(qǐng)求,以便查看你的 API 服務(wù)是否會(huì)接受來(lái)自此客戶(hù)域的客戶(hù)端請(qǐng)求。
“跨域資源共享(CORS)是一種基于 HTTP 頭的機(jī)制,這種機(jī)制允許服務(wù)器標(biāo)記除自身源外的其他任何來(lái)源(域、方案或端口)。而對(duì)于這些被服務(wù)器標(biāo)識(shí)的源,瀏覽器應(yīng)該允許我們從它們加載資源。”
另外,有很多用于 Node.js 的輔助庫(kù)來(lái) 幫助 API 開(kāi)發(fā)者處理 CORS。
3、身份認(rèn)證與 JSON Web 令牌
有多種方法可以驗(yàn)證你的 API 中的認(rèn)證用戶(hù),但最好的方法之一是使用 JSON Web 令牌(JWT),而這些令牌使用各種知名的加密庫(kù)進(jìn)行簽名。
當(dāng)客戶(hù)端登錄時(shí),身份管理服務(wù)會(huì)向客戶(hù)端提供一個(gè) JWT。然后,客戶(hù)端可以使用這個(gè)令牌向 API 發(fā)出請(qǐng)求,API 收到請(qǐng)求后,從服務(wù)器讀取公鑰或私密信息來(lái)驗(yàn)證這個(gè)令牌。
有一些現(xiàn)有的庫(kù),可以幫助我們對(duì)令牌進(jìn)行驗(yàn)證,包括 jsonwebtoken。關(guān)于 JWT 的更多信息,以及各種語(yǔ)言中對(duì)其的支持庫(kù),請(qǐng)查看 JWT.io。
import jwt from 'jsonwebtoken'
export default function (req, res, next) {
// req.headers.authorization Bearer token
const token = extractToken(req)
jwt.verify(token, SECRET, { algorithms: ['HS256'] }, (err, decoded) => {
if (err) { next(err) }
req.session = decoded
next()
})
}
4、授權(quán)與作用域
認(rèn)證(或身份驗(yàn)證)很重要,但授權(quán)同樣很重要。也就是說(shuō),經(jīng)過(guò)驗(yàn)證的客戶(hù)端是否有權(quán)限讓服務(wù)器執(zhí)行某個(gè)請(qǐng)求呢?這就是作用域的價(jià)值所在。當(dāng)身份管理服務(wù)器對(duì)客戶(hù)端進(jìn)行身份認(rèn)證,且創(chuàng)建 JWT 令牌時(shí),身份管理服務(wù)會(huì)給當(dāng)前客戶(hù)提供一個(gè)作用域,這個(gè)作用域?qū)?huì)決定當(dāng)前經(jīng)過(guò)驗(yàn)證的客戶(hù)的 API 請(qǐng)求能否被服務(wù)器執(zhí)行。這樣也就免去了服務(wù)器對(duì)訪(fǎng)問(wèn)控制列表的一些不必要的查詢(xún)。
作用域是一個(gè)文本塊(通常以空格分隔),用于描述一個(gè) API 端點(diǎn)的訪(fǎng)問(wèn)能力。一般來(lái)說(shuō),作用域被分為資源與動(dòng)作。這種模式對(duì) REST/JSON API 很有效,因?yàn)樗鼈冇邢嗨频?nbsp;RESOURCE:ACTION
結(jié)構(gòu)。(例如,ARTICLE:WRITE
或 ARTICLE:READ
,其中 ARTICLE
是資源,READ
和 WRITE
是動(dòng)作)。
作用域的劃分讓我們的 API 能夠?qū)W⒂诠δ艿膶?shí)現(xiàn),而不是去考慮各種角色和用戶(hù)。身份訪(fǎng)問(wèn)管理服務(wù)可以將不同的角色和用戶(hù)分配不同的權(quán)限范圍,然后再將這些不同的作用域提供給不同的 JWT 驗(yàn)證中的客戶(hù)。
總結(jié)
當(dāng)我們開(kāi)發(fā)和部署 API 時(shí),安全應(yīng)該一直是最重要的要求之一。雖然安全性是一個(gè)比較寬泛的話(huà)題,但如果能解決上面四個(gè)方面的問(wèn)題,這對(duì)于你的 API 來(lái)說(shuō),在生產(chǎn)環(huán)境中將會(huì)表現(xiàn)得更好。
文檔
有什么能比沒(méi)有文檔更糟糕?過(guò)期的文檔。
開(kāi)發(fā)者對(duì)文檔真的是又愛(ài)又恨。盡管如此,文檔仍然是 API 定義是否完善的一個(gè)關(guān)鍵部分。開(kāi)發(fā)者需要從文檔中知道如何使用這些 API,且你創(chuàng)建的文檔對(duì)于開(kāi)發(fā)者如何更好地使用 API 也有著非常巨大的作用。
創(chuàng)建 API 文檔,我們需要關(guān)注下面三個(gè)方面:
- 開(kāi)發(fā)者入門(mén)文檔(自述文件/基本介紹)
- 技術(shù)參考(規(guī)范/說(shuō)明書(shū))
- 使用方法(入門(mén)和其他指南)
1、入門(mén)文檔
在構(gòu)建 API 服務(wù)的時(shí)候,你需要明確一些事情,比如:這個(gè) API 是做什么的?如何建立開(kāi)發(fā)者環(huán)境?如何測(cè)試該服務(wù)?如何提交問(wèn)題?如何部署它?
通常我們可以通過(guò)自述(README
)文件來(lái)回答上面的這些問(wèn)題,自述文件一般放在你的代碼庫(kù)中,用于為開(kāi)發(fā)者提供使用你項(xiàng)目的最基本的起點(diǎn)和說(shuō)明。
自述文件應(yīng)該包含:
- API 的描述
- 技術(shù)參考與指南的鏈接
- 如何以開(kāi)發(fā)者的身份設(shè)置你的項(xiàng)目
- 如何測(cè)試這個(gè)項(xiàng)目
- 如何部署這個(gè)項(xiàng)目
- 依賴(lài)管理
- 代碼貢獻(xiàn)指南
- 行為準(zhǔn)則
- 許可證
- 致謝
你的自述文件應(yīng)該簡(jiǎn)明扼要;你不必解釋每一個(gè)方面,但要提供足夠的信息,以便開(kāi)發(fā)者在熟悉你的項(xiàng)目后可以進(jìn)一步深入研究。
2、技術(shù)參考
在 REST/JSON API 中, 每一個(gè)具體的端點(diǎn)都對(duì)應(yīng)一個(gè)特定的功能,都需要一個(gè)具體的說(shuō)明文檔,這非常重要。文檔中會(huì)定義 API 的描述,輸入和可能的輸出,并為各種客戶(hù)端提供使用示例。
OpenAPI 是一個(gè)創(chuàng)建 REST/JSON 文檔的標(biāo)準(zhǔn), 它可以指導(dǎo)你完成編寫(xiě) API 文檔所需的各種細(xì)節(jié)。OpenAPI 還可以為你的 API 生成演示文檔。
3、使用方法
對(duì)于 API 的用戶(hù)來(lái)說(shuō),僅僅只有技術(shù)說(shuō)明是不夠的。他們還需要知道如何在一些特定的情況和場(chǎng)景下來(lái)使用這些 API,而大多數(shù)的潛在用戶(hù)可能希望通過(guò)你的 API 來(lái)解決他們所遇到的問(wèn)題。
向用戶(hù)介紹 API 的一個(gè)好的方法是利用一個(gè)“開(kāi)始”頁(yè)面。“開(kāi)始”頁(yè)面可以通過(guò)一個(gè)簡(jiǎn)單的用例引導(dǎo)用戶(hù),讓他們迅速了解你的 API 能給他們能帶來(lái)的益處。
總結(jié)
對(duì)于任何完善的 API,文檔都是一個(gè)很關(guān)鍵的組成部分。當(dāng)你在創(chuàng)建文檔時(shí),你需要關(guān)注 API 文檔中的如何入門(mén)、技術(shù)參考以及如何快速開(kāi)始等三個(gè)方面,這樣你的 API 才算是一個(gè)完善的 API。
驗(yàn)證
API 開(kāi)發(fā)過(guò)程中經(jīng)常被忽視的一個(gè)點(diǎn)就是驗(yàn)證。它是一個(gè)驗(yàn)證來(lái)自外部來(lái)源的輸入的過(guò)程。這些來(lái)源可以是客戶(hù)端發(fā)送過(guò)來(lái)的 JSON 數(shù)據(jù),或者是你請(qǐng)求別人的服務(wù)收到的響應(yīng)數(shù)據(jù)。我們不僅僅要檢查這些數(shù)據(jù)的類(lèi)型,還要確保這些數(shù)據(jù)確實(shí)是我們要的數(shù)據(jù),這樣可以消除很多潛在的問(wèn)題。了解你的邊界以及你能控制的和不能控制的東西,對(duì)于 API 的數(shù)據(jù)驗(yàn)證來(lái)說(shuō)是一個(gè)很重要的方面。
最好的策略是在進(jìn)入數(shù)據(jù)邏輯處理之前,在你能控制的邊界的邊緣處進(jìn)行數(shù)據(jù)的驗(yàn)證。當(dāng)客戶(hù)端向你的 API 發(fā)送數(shù)據(jù)時(shí),你需要對(duì)該數(shù)據(jù)做出任何處理之前應(yīng)用你的驗(yàn)證,比如:確保 Email 是真實(shí)的郵件地址、日期數(shù)據(jù)有正確的格式、字符串符合長(zhǎng)度要求。
這種簡(jiǎn)單的檢查可以為你的應(yīng)用增加安全性和一致性。還有,當(dāng)你從某個(gè)服務(wù)接收數(shù)據(jù)時(shí),比如數(shù)據(jù)庫(kù)或緩存,你需要重新驗(yàn)證這些數(shù)據(jù),以確保返回的結(jié)果符合你的數(shù)據(jù)檢查。
你可以自己手寫(xiě)這些校驗(yàn)邏輯,當(dāng)然也可以用像 Lodash 或 Ramda 這樣的函數(shù)庫(kù),它們對(duì)于一些小的數(shù)據(jù)對(duì)象非常有用。像 Joi、Yup 或 Zod 這樣的驗(yàn)證庫(kù)效果會(huì)更好,因?yàn)樗鼈儼艘恍┏R?jiàn)的驗(yàn)證方法,可以節(jié)省你的時(shí)間和精力。除此,它們還能創(chuàng)建一個(gè)可讀性強(qiáng)的模式。如果你需要看看與語(yǔ)言無(wú)關(guān)的東西,請(qǐng)看 JSON Schema。
總結(jié)
數(shù)據(jù)驗(yàn)證雖然并不顯眼和突出(LCTT 譯注:跟 API 的功能實(shí)現(xiàn)以及其他幾個(gè)方面比),但它可以幫你節(jié)省大量的時(shí)間。如果不做驗(yàn)證,這些時(shí)間將可能被用于故障排除和編寫(xiě)數(shù)據(jù)遷移腳本。真的不要相信你的客戶(hù)端會(huì)發(fā)送干凈的數(shù)據(jù)給你,也不要讓驗(yàn)證不通過(guò)的數(shù)據(jù)滲入到你的業(yè)務(wù)邏輯或持久數(shù)據(jù)存儲(chǔ)中去。花點(diǎn)時(shí)間驗(yàn)證你的 API 收到的數(shù)據(jù)和請(qǐng)求到的響應(yīng)數(shù)據(jù),雖然在前期你可能會(huì)感到一些挫折和不適,但這總比你在后期花大量時(shí)間去做各種數(shù)據(jù)收緊管制、故障排除等要容易得多。
測(cè)試
測(cè)試是軟件開(kāi)發(fā)中的最佳實(shí)踐,它應(yīng)該是最主要的非功能性的要求。對(duì)于包括 API 在內(nèi)的任何項(xiàng)目,確定測(cè)試策略都是一個(gè)挑戰(zhàn),因?yàn)槟阕允贾两K都要掌握各種約束,以便相應(yīng)的來(lái)制定你的測(cè)試策略。
集成測(cè)試是測(cè)試 API 的最有效的方法之一。在集成測(cè)試模式中,開(kāi)發(fā)團(tuán)隊(duì)會(huì)創(chuàng)建一個(gè)測(cè)試集用來(lái)覆蓋應(yīng)用流程中的某些部分,從一個(gè)點(diǎn)到另一個(gè)點(diǎn)。一個(gè)好的集成測(cè)試流程包括測(cè)試 API 的入口點(diǎn)以及模擬請(qǐng)求到服務(wù)端的響應(yīng)。搞定這兩點(diǎn),你就覆蓋了整個(gè)邏輯,包括從 API 請(qǐng)求的開(kāi)始到模擬服務(wù)器的響應(yīng),并返回?cái)?shù)據(jù)給 API。
雖然使用的是模擬,但這種方法讓能我們專(zhuān)注于代碼邏輯層,而不需要去依賴(lài)后端服務(wù)和展示邏輯來(lái)進(jìn)行測(cè)試。沒(méi)有依賴(lài)的測(cè)試會(huì)更加可靠、更容易實(shí)現(xiàn)自動(dòng)化,且更容易被接入持續(xù)集成管道流。
集成測(cè)試的實(shí)施中,我會(huì)用 Tape、Test-server 和 Fetch-mock。這些庫(kù)讓我們能夠從 API 的請(qǐng)求到數(shù)據(jù)的響應(yīng)進(jìn)行隔離測(cè)試,使用 Fetch-mock 還可以將出站請(qǐng)求捕獲到持久層。
總結(jié)
雖然其他的各種測(cè)試和類(lèi)型檢查對(duì) API 也都有很好的益處,但集成測(cè)試在流程效率、構(gòu)建和管理時(shí)間方面卻有著更大的優(yōu)勢(shì)。使用 Fetch-mock 這樣的工具,可以在服務(wù)邊界提供一個(gè)干凈的模擬場(chǎng)景。
專(zhuān)注于基礎(chǔ)
不管是設(shè)計(jì)和構(gòu)建應(yīng)用程序還是 API,都要確保包含上面的四個(gè)基本要素。它們并不是我們唯一需要考慮的非功能性需求,其他的還包括應(yīng)用監(jiān)控、日志和 API 管理等。即便如此,安全、文檔、驗(yàn)證和測(cè)試這四個(gè)基本點(diǎn),對(duì)于構(gòu)建任何使用場(chǎng)景下的完善 API 都是至關(guān)重要的關(guān)注點(diǎn)。