使用Node.js构建API网关

当微服务架构中的服务需要外部客户端访问时,它们在身份验证和传输方面具有一些共同的要求。 API网关提供了一个共享层来处理服务协议之间的差异,并满足台式机浏览器,移动设备和旧版系统等特定客户端的要求。

微服务是面向服务的体系结构,团队可以在其中独立设计,开发和交付其应用程序。 它允许在系统的各个级别上实现技术多样性,在这种情况下,团队可以通过使用最佳语言,数据库,协议和传输层来应对特定的技术挑战,从而从中受益。 例如,一个团队可以在HTTP REST上使用JSON,而另一个团队可以在HTTP / 2上使用gRPC或RabbitMQ之类的消息传递代理。

在某些情况下,使用不同的数据序列化和协议可能很强大,但是想要使用我们产品的客户可能有不同的要求 。 在具有同类技术堆栈的系统中也会出现此问题,因为消费者可能从台式机浏览器到移动设备和游戏机,再到旧版系统,都将发生变化。 一个客户端可能期望XML格式,而另一个客户端则需要JSON。 在许多情况下,您都需要同时支持。

当客户端要使用微服务时,您可能面临的另一个挑战来自诸如身份验证之类的通用共享逻辑 ,因为您不想在所有服务中重新实现相同的功能。

总结:我们不想以支持多个客户端并重新实现相同逻辑的方式在微服务架构中实现内部服务。 这是API网关出现的地方,并提供了一个共享层来处理服务协议之间的差异并满足特定客户端的要求。

API网关是微服务体系结构中的一种服务,它为客户端提供了一个共享层和API,以便客户端与内部服务进行通信。 API网关可以路由请求 ,转换协议, 聚合数据实现诸如身份验证和速率限制器之类的共享逻辑

您可以将API Gateway视为我们微服务世界的切入点 。 我们的系统可以具有一个或多个API网关,具体取决于客户的要求。 例如,我们可以为台式机浏览器,移动应用程序和公共API提供单独的网关。

API网关作为微服务的入口点

由于API Gateway为诸如浏览器之类的客户端应用程序提供功能-它可以由负责前端应用程序的团队实施和管理。

这也意味着应由负责特定客户端的团队选择以语言实现的API网关的语言。 由于JavaScript是开发用于浏览器的应用程序的主要语言,因此即使您的微服务架构是用其他语言开发的,Node.js也是实现API网关的绝佳选择。

Netflix成功使用Node.js API网关及其Java后端来支持广泛的客户-要了解有关其方法的更多信息,请阅读Netflix上的“微服务的PaaS铺平的道路”一文。

Netflix处理不同客户的方法, 来源

前面我们讨论了可以将通用共享逻辑放入API网关,本节将介绍最常见的网关职责。

我们将API网关定义为您的微服务的入口点。 在网关服务中,您可以将请求从客户端路由到特定服务。 您甚至可以在路由过程中处理版本控制 ,也可以更改后端接口,而公共暴露的接口可以保持不变。 您还可以在与多种服务协作的API网关中定义新的端点。

API网关作为微服务入口点

API网关方法还可以帮助您分解整体应用程序。 在大多数情况下,从头开始将系统作为微服务重写是一个好主意,也是不可能的,因为我们需要在过渡期间为业务提供功能。

在这种情况下,我们可以将代理或API网关放在我们的整体应用程序前面,并实现新功能(如微服务),并将新端点路由到新服务,而我们可以通过整体服务旧端点。 稍后,我们还可以通过将现有功能转移到新服务中来分解整体。

通过渐进式设计,我们可以从整体架构向微服务平稳过渡

API Gateway的进化设计

大多数微服务基础架构都需要处理身份验证。 将身份验证之类的共享逻辑放入API网关可以帮助您使服务小型化专注于域

在微服务架构中,您可以通过网络配置服务保护在DMZ (非军事区)中 ,并通过API网关 公开给客户端。 该网关还可以处理多种身份验证方法。 例如,您可以同时支持基于cookie令牌的身份验证。

具有身份验证的API网关

在微服务体系结构中,可能会发生客户端需要不同聚合级别的数据的情况 ,例如对各种微服务中发生的数据实体进行规范化 。 在这种情况下,我们可以使用我们的API网关来解决这些依赖性并从多个服务中收集数据。

在下图中,您可以看到API Gateway如何合并并将用户和信用信息作为一条数据返回给客户端。 请注意,它们由不同的微服务拥有和管理。

我们可能需要支持具有不同数据序列化格式要求的客户端。

想象一下一种情况,我们的微服务使用JSON,但是我们的一位客户只能使用XML API。 在这种情况下,我们可以将JSON转换为XML转换到API网关中,而不是在所有微服务中实现它。

微服务体系结构允许多语言协议传输获得不同技术的好处。 但是,大多数客户端仅支持一种协议。 在这种情况下,我们需要为客户端转换服务协议。

API网关还可以处理客户端和微服务之间的协议转换。

在下一个图像中,您可以看到当我们的内部微服务使用gRPC和GraphQL时,客户端如何期望通过HTTP REST进行的所有通信。

在前面的示例中,您可以看到我们可以将通用的共享逻辑(如身份验证)放入API网关。 除了身份验证,您还可以在API Gateway中实现速率限制,缓存和各种可靠性功能。

实施API网关时,应避免将非通用逻辑(例如,特定于域的数据转换)放入网关。

服务应始终对其数据域拥有完全所有权 。 构建一个雄心勃勃的API网关需要服务团队的控制权,这违背了微服务的理念。

这就是为什么您应该谨慎使用API​​网关中的数据聚合的原因-它虽然功能强大,但也会导致应避免的特定于域的数据转换或规则处理逻辑。

始终为API网关定义明确的职责 ,并且仅在其中包含通用共享逻辑。

当您想在API网关中做一些简单的事情(例如将请求路由到特定服务)时,可以使用反向代理(例如nginx)。 但有时,您可能需要实现一般代理不支​​持的逻辑。 在这种情况下,您可以在Node.js中实现自己的 API网关。

在Node.js中,您可以使用http-proxy包简单地将请求代理到特定服务,也可以使用功能更丰富的express-gateway创建API网关。

在我们的第一个API网关示例中,我们先对请求进行身份验证,然后再将其代理到用户服务。

 const express = require('express') const httpProxy = require('express-http-proxy') const app = express() const userServiceProxy = httpProxy('https://user-service') // Authentication app.use((req, res, next) => { // TODO: my authentication logic next() }) // Proxy request app.get('/users/:userId', (req, res, next) => { userServiceProxy(req, res, next) }) 

另一种方法可以是当您在API网关中发出新请求并将响应返回给客户端时:

 const express = require('express') const request = require('request-promise-native') const app = express()  const express = require('express') const request = require('request-promise-native') const app = express() // Resolve: 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) }) // Resolve: 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) }) 

API网关提供了一个共享层,可通过微服务架构满足客户需求。 它有助于使您的服务保持较小规模并专注于域。 您可以在API网关中放置不同的通用逻辑,但是应避免使用过于夸张的API网关,因为它们会受到服务团队的控制。


最初于 2017 年8月3日 发布在 blog.risingstack.com 上。