快上車!使用 Node.js 搭建一個(gè) API 網(wǎng)關(guān)
外部客戶端訪問微服務(wù)架構(gòu)中的服務(wù)時(shí),服務(wù)端會(huì)對認(rèn)證和傳輸有一些常見的要求。API 網(wǎng)關(guān)提供共享層來處理服務(wù)協(xié)議之間的差異,并滿足特定客戶端(如桌面瀏覽器、移動(dòng)設(shè)備和老系統(tǒng))的要求。
微服務(wù)和消費(fèi)者
微服務(wù)是面向服務(wù)的架構(gòu),團(tuán)隊(duì)可以獨(dú)立設(shè)計(jì)、開發(fā)和發(fā)布應(yīng)用程序。它允許在系統(tǒng)各個(gè)層面上的技術(shù)多樣性,團(tuán)隊(duì)可以在給定的技術(shù)難題中使用最佳語言、數(shù)據(jù)庫、協(xié)議和傳輸層,從而受益。例如,一個(gè)團(tuán)隊(duì)可以使用 HTTP REST 上的 JSON,而另一個(gè)團(tuán)隊(duì)可以使用 HTTP/2 上的 gRPC 或 RabbitMQ 等消息代理。
在某些情況下使用不同的數(shù)據(jù)序列化和協(xié)議可能是強(qiáng)大的,但要使用我們的產(chǎn)品的客戶可能有不同的需求。該問題也可能發(fā)生在具有同質(zhì)技術(shù)棧的系統(tǒng)中,因?yàn)榭蛻艨梢詮淖烂鏋g覽器通過移動(dòng)設(shè)備和游戲機(jī)到遺留系統(tǒng)。一個(gè)客戶可能期望 XML 格式,而另一個(gè)客戶可能希望 JSON 。在許多情況下,你需要同時(shí)支持它們。
當(dāng)客戶想要使用你的微服務(wù)時(shí),你可以面對的另一個(gè)挑戰(zhàn)來自于通用的共享邏輯(如身份驗(yàn)證),因?yàn)槟悴幌朐谒蟹?wù)中重新實(shí)現(xiàn)相同的事情。
總結(jié):我們不想在我們的微服務(wù)架構(gòu)中實(shí)現(xiàn)我們的內(nèi)部服務(wù),以支持多個(gè)客戶端并可以重復(fù)使用相同的邏輯。這就是 API 網(wǎng)關(guān)出現(xiàn)的原因,其作為共享層來處理服務(wù)協(xié)議之間的差異并滿足特定客戶端的要求。
什么是 API 網(wǎng)關(guān)?
API 網(wǎng)關(guān)是微服務(wù)架構(gòu)中的一種服務(wù),它為客戶端提供共享層和 API,以便與內(nèi)部服務(wù)進(jìn)行通信。API 網(wǎng)關(guān)可以進(jìn)行路由請求、轉(zhuǎn)換協(xié)議、聚合數(shù)據(jù)以及實(shí)現(xiàn)共享邏輯,如認(rèn)證和速率限制器。
你可以將 API 網(wǎng)關(guān)視為我們的微服務(wù)世界的入口點(diǎn)。
我們的系統(tǒng)可以有一個(gè)或多個(gè) API 網(wǎng)關(guān),具體取決于客戶的需求。例如,我們可以為桌面瀏覽器、移動(dòng)應(yīng)用程序和公共 API 提供單獨(dú)的網(wǎng)關(guān)。
API 網(wǎng)關(guān)作為微服務(wù)的切入點(diǎn)
Node.js 用于前端團(tuán)隊(duì)的 API 網(wǎng)關(guān)
由于 API 網(wǎng)關(guān)為客戶端應(yīng)用程序(如瀏覽器)提供了功能,它可以由負(fù)責(zé)開發(fā)前端應(yīng)用程序的團(tuán)隊(duì)實(shí)施和管理。
這也意味著用哪種語言實(shí)現(xiàn) API Gateway 應(yīng)由負(fù)責(zé)特定客戶的團(tuán)隊(duì)選擇。由于 JavaScript 是開發(fā)瀏覽器應(yīng)用程序的主要語言,即使你的微服務(wù)架構(gòu)以不同的語言開發(fā),Node.js 也可以成為實(shí)現(xiàn) API 網(wǎng)關(guān)的絕佳選擇。
Netflix 成功地使用 Node.js API 網(wǎng)關(guān)及其 Java 后端來支持廣泛的客戶端 ,如果想要了解更多關(guān)于它們的方法,可以看看這篇文章 The "Paved Road" PaaS for Microservices at Netflix

API 網(wǎng)關(guān)功能
我們之前討論過,可以將通用共享邏輯放入你的 API 網(wǎng)關(guān),本節(jié)將介紹最常見的網(wǎng)關(guān)職責(zé)。
路由和版本控制
我們將 API網(wǎng)關(guān)定義為你的微服務(wù)的入口點(diǎn)。在網(wǎng)關(guān)服務(wù)中,你可以指定從客戶端路由到特定服務(wù)的路由請求。甚至可以通過路由處理版本或更改后端接口,而公開的接口可以保持不變。你還可以在你的API網(wǎng)關(guān)中定義與多個(gè)服務(wù)配合的新端點(diǎn)。
API 網(wǎng)關(guān)作為微服務(wù)入口點(diǎn)
網(wǎng)關(guān)設(shè)計(jì)的進(jìn)化
API網(wǎng)關(guān)方法可以幫助你分解整體應(yīng)用程序。在大多數(shù)情況下,作為微服務(wù)端,重構(gòu)系統(tǒng)并不是一個(gè)好主意,而且也是不可能的,因?yàn)槲覀冃枰谶^渡期間為業(yè)務(wù)提供功能。
在這種情況下,我們可以將代理或 API 網(wǎng)關(guān)置于我們的整體應(yīng)用程序之前,將新功能作為微服務(wù)實(shí)現(xiàn),并將新端點(diǎn)路由到新服務(wù),同時(shí)通過原有的路由服務(wù)舊端點(diǎn)。這樣以后,我們也可以通過將原有功能轉(zhuǎn)變?yōu)樾路?wù)來分解整體。
通過漸進(jìn)式設(shè)計(jì),我們可以從整體架構(gòu)平穩(wěn)過渡到微服務(wù)。

認(rèn)證方式
大多數(shù)微服務(wù)基礎(chǔ)架構(gòu)都需要處理身份驗(yàn)證。將身份驗(yàn)證之類的共享邏輯放入API網(wǎng)關(guān)可以幫助你縮小服務(wù)的體積并專注管理域。
在微服務(wù)架構(gòu)中,你可以通過網(wǎng)絡(luò)配置將服務(wù)保留在DMZ(保護(hù)區(qū))中,并通過API網(wǎng)關(guān)將其公開給客戶端。該網(wǎng)關(guān)還可以處理多種身份驗(yàn)證方法,例如,你可以同時(shí)支持基于cookie和token的身份驗(yàn)證。

數(shù)據(jù)匯總
在微服務(wù)體系結(jié)構(gòu)中,可能會(huì)發(fā)生客戶端需要不同聚合級(jí)別的數(shù)據(jù)的情況,例如對各種微服務(wù)中產(chǎn)生的數(shù)據(jù)實(shí)體進(jìn)行非規(guī)范化。在這種情況下,我們可以使用我們的API網(wǎng)關(guān)來解決這些依賴關(guān)系并從多個(gè)服務(wù)中收集數(shù)據(jù)。
在下圖中,你可以看到API 網(wǎng)關(guān) 如何合并用戶和信用信息,并作為一條數(shù)據(jù)返回給客戶端。請注意,它們由不同的微服務(wù)擁有和管理。

序列化格式轉(zhuǎn)換
我們可能需要支持具有不同數(shù)據(jù)序列化格式要求的客戶端。想象一下這種情況:我們的微服務(wù)使用JSON,但是我們的一位客戶只能使用XML API。在這種情況下,我們可以在API網(wǎng)關(guān)中將JSON轉(zhuǎn)換為XML,而不是在所有微服務(wù)中去實(shí)現(xiàn)。

協(xié)議轉(zhuǎn)換
微服務(wù)架構(gòu)允許多語言協(xié)議傳輸從而獲得不同技術(shù)的好處。但是,大多數(shù)客戶端僅支持一種協(xié)議。在這種情況下,我們需要為客戶端轉(zhuǎn)換服務(wù)協(xié)議。
API 網(wǎng)關(guān)還可以處理客戶端和微服務(wù)器之間的協(xié)議轉(zhuǎn)換。
在下一張圖片中,你可以看到客戶端希望通過 HTTP REST 進(jìn)行的所有通信,而內(nèi)部的微服務(wù)使用 gRPC 和 GraphQL 。

限速和緩存
在前面的例子中,你可以看到我們可以把通用的共享邏輯(如身份驗(yàn)證)放在 API 網(wǎng)關(guān)中。除了身份驗(yàn)證之外,你還可以在 API 網(wǎng)關(guān)中實(shí)現(xiàn)速率限制,緩存以及各種可靠性功能。
超負(fù)荷的 API 網(wǎng)關(guān)
實(shí)現(xiàn)API網(wǎng)關(guān)時(shí),應(yīng)避免將非通用邏輯(例如特定領(lǐng)域的數(shù)據(jù)轉(zhuǎn)換)放入網(wǎng)關(guān)。服務(wù)應(yīng)始終對其數(shù)據(jù)域擁有完全所有權(quán)。構(gòu)建一個(gè)超負(fù)荷的API網(wǎng)關(guān),讓微服務(wù)團(tuán)隊(duì)來控制,這違背了微服務(wù)的理念。
這就是為什么你應(yīng)該謹(jǐn)慎使用API網(wǎng)關(guān)中的數(shù)據(jù)聚合的原因,使用起來可能功能強(qiáng)大,但也應(yīng)避免的特定于域的數(shù)據(jù)轉(zhuǎn)換或規(guī)則處理邏輯。
始終為你的 API 網(wǎng)關(guān)定義明確的責(zé)任,并且只包括其中的通用共享邏輯。
Node.js API 網(wǎng)關(guān)
當(dāng)你希望在 API 網(wǎng)關(guān)中執(zhí)行簡單的操作,比如將請求路由到特定服務(wù),你可以使用像 nginx 這樣的反向代理。但在某些時(shí)候,你可能需要實(shí)現(xiàn)一般代理不支持的邏輯。在這種情況下,你可以在 Node.js 中實(shí)現(xiàn)自己的 API 網(wǎng)關(guān)。
在 Node.js 中,你可以使用 http-proxy 軟件包簡單地代理對特定服務(wù)的請求,也可以使用更多豐富功能的 express-gateway 來創(chuàng)建 API 網(wǎng)關(guān)。
在我們的第一個(gè) API 網(wǎng)關(guān)示例中,我們在將代碼委托給 user 服務(wù)之前驗(yàn)證請求。
const express = require('express')
const httpProxy = require('express-http-proxy')
const app = express()
const userServiceProxy = httpProxy('https://user-service')
// 身份認(rèn)證
app.use((req, res, next) => {
// TODO: 身份認(rèn)證邏輯
next()
})
// 代理請求
app.get('/users/:userId', (req, res, next) => {
userServiceProxy(req, res, next)
})
另一種示例可能是在你的 API 網(wǎng)關(guān)中發(fā)出新的請求,并將響應(yīng)返回給客戶端:
const express = require('express')
const request = require('request-promise-native')
const app = express()
// 解決: GET /users/me
app.get('/users/me', async (req, res) => {
const userId = req.session.userId
const uri = `https://user-service/users/${userId}`
const user = await request(uri)
res.json(user)
})
Node.js API 網(wǎng)關(guān)總結(jié)
API 網(wǎng)關(guān)提供了一個(gè)共享層,以通過微服務(wù)架構(gòu)來滿足客戶需求。它有助于保持你的服務(wù)小而專注。你可以將不同的通用邏輯放入你的 API 網(wǎng)關(guān),但是你應(yīng)該避免API網(wǎng)關(guān)的過度使用,因?yàn)楹芏噙壿嬁梢詮姆?wù)團(tuán)隊(duì)中獲得控制。
譯文:Building an API Gateway using Node.js
地址:https://blog.risingstack.com/building-an-api-gateway-using-nodejs/
