所有文章 > API开发 > 如何在 Node.js 中构建 gRPC API
如何在 Node.js 中构建 gRPC API

如何在 Node.js 中构建 gRPC API

gRPC是用于在分布式环境中构建服务到服务通信系统的最流行的 API 架构模式之一。它是 Google 于 2015 年发布的高性能、开源且通用的 RPC 框架。它基于 HTTP/2,通常用于高效的双向客户端到服务器通信以及双工数据流。

Node.js 是一个开源跨平台 JavaScript 运行时,它基于 Chrome 的 v8 JavaScript 引擎构建,能够在 Web 浏览器之外执行 JavaScript 代码。

在本教程中,您将学习如何在 Node.js 中构建一个简单的 gRPC 服务,该服务对产品执行 CRUD 操作。您将首先使用 Protobuf 编写服务定义,描述服务公开的方法。然后,您将在服务器上编写这些服务方法的实现逻辑。

在开始编写代码之前,我们将回顾 gRPC 服务的组件以及它们如何协同工作。

什么是 gRPC 服务?

gRPC 服务使用 gRPC 协议促进客户端和服务器之间的通信。 gRPC 服务通常具有三个关键组件:

  • 服务定义
  • 客户端
  • 服务器

服务定义

在 gRPC 中,service关键字用于定义服务。此服务定义包括客户端可以在服务器上远程调用的一个或多个远程过程调用 (RPC) 方法,以及每个方法的请求和响应消息。服务定义通常以与.proto语言无关的格式(称为协议缓冲区 (Protobuf))编写在文件中。Protobuf 将数据序列化为二进制形式,使其更紧凑,因此可以更快地通过网络传输。

服务器

在 gRPC 中,服务器会监听并响应来自客户端的 RPC 调用。服务器会实现.proto文件中定义的服务,并包含实际的实现逻辑。此逻辑可能会处理现有数据的变异、新数据的创建或从数据库中提取数据。

客户端

gRPC 中的客户端是使用 RPC 框架连接到 gRPC 服务器并与之通信的应用程序。与服务器类似,客户端使用 Protobuf 进行数据序列化和反序列化。

先决条件

为了跟随本教程,您需要:

  • 充分理解 JavaScript。
  • 具有使用 Node.js 和 npm/yarn 的基本经验。在此处下载Node.js。
  • Postman 应用程序,或访问网络版本。

步骤 1:设置你的项目

首先,为该项目创建一个新目录并初始化一个 Node.js 应用程序:

mkdir node-gRPC-service

cd 节点-gRPC 服务

npm init -y

现在,您需要安装构建 gRPC 服务所需的依赖项。这些依赖项包括:

使用以下代码安装这些依赖项:

npm 安装 @gRPC/gRPC-js @gRPC/proto-loader

创建您将在此项目中主要处理的两个文件,分别是product.protoserver.js

触摸product.proto server.js

第 2 步:定义服务

接下来,您将在文件中编写服务定义product.proto。如上所述,gRPC 使用 Protobuf 作为其接口定义语言。

首先,指定您将使用的 Protobuf 语法 – 并使用package关键字为您的服务声明命名空间:

语法=“proto3”;

包装产品;

接下来,声明客户端可以在您的服务中远程调用的 RPC 方法。在本教程中,您将声明五种不同的方法对产品执行不同的操作:createProduct、、、和readProductreadProducts这些方法可以使用以下代码声明:updateProductdeleteProduct

服务产品 {
rpc CreateProduct (ProductItem) 返回 (ProductItem);
rpc ReadProduct(ProductId) 返回(ProductItem);
rpc ReadProducts (VoidParam) 返回 (ProductItems);
rpc UpdateProduct(ProductItem) 返回(ProductItem);
rpc DeleteProduct(ProductId) 返回(DeleteProductResponse);
}

在此服务中,我们使用几种消息类型作为这些方法的输入和输出参数:

  • VoidParam:这是我们传递给 RPC 的空消息,它不需要客户端输入。
  • ProductID:这是特定产品的 ID。它是一个整数。
  • ProductItem:包含有关单个产品的信息。
  • ProductItems:这是单个产品的数组。
  • DeleteProductResponse:这是一个布尔值,指示产品删除是否成功或失败。

这些消息可以用以下代码来定义:

消息 VoidParam {}

消息产品编号 {
int32 ID=1;
}

枚举类别 {
智能手机=1;
相机=2;
笔记本电脑=3;
耳机 = 4;
充电器=5;
扬声器 = 6;
电视机=7;
调制解调器=8;
键盘 = 9;
麦克风=10;
}
消息产品项目 {
int32 ID=1;
字符串名称 = 2;
字符串描述=3;
浮动价格=4;
类别category = 5;
}

消息产品项目 {
重复ProductItem产品=1;
}

消息 DeleteProductResponse {
bool 已删除 = 1;
}

步骤 3:设置服务器

现在,您将使用 gRPC-js 和 proto-loader 包设置 gRPC 服务器。导航到server.js以下代码并粘贴:

const gRPC = require('@gRPC/gRPC-js');
const protoLoader = require(“@gRPC/proto-loader”);

const packageDef = protoLoader.loadSync("product.proto", {});
const gRPCObject = gRPC.loadPackageDefinition(packageDef);

const productPackage = gRPCObject.产品;

const 产品 = [];

函数 createProduct(调用,回调) {}
函数 readProduct(调用,回调){}
函数 readProducts(调用,回调){}
函数 updateProduct(调用,回调) {}
函数 deleteProduct(调用,回调) {}

const 服务器 = 新的 gRPC.Server();
服务器.addService(产品包.产品.服务,{
创建产品,
读取产品,
阅读产品,
更新产品,
删除产品,
});

服务器.bindAsync("0.0.0.0:4000", gRPC.ServerCredentials.createInsecure(), () => {
服务器.启动();
});

这段代码首先.proto使用该方法加载服务定义文件loadSync,并将其转换为 gRPC 对象。然后从该 gRPC 对象中引用产品包。

products数组将作为您的数据库。您将从此数组中添加、删除、更新和查询对象。

空数组的正下方是服务函数,我们将在其中添加在 中声明的服务方法的实现逻辑product.proto。这些函数接受两个参数:callcallback

参数call引用传入的请求对象,其中包括随该请求发送的所有参数。这callback是服务器处理程序在处理完请求后调用的函数。它用于将响应发送回客户端或在发生错误时传达错误。该callback函数的第一个参数接受一个错误对象(或者null如果没有错误)。第二个参数是响应对象,用于将数据返回给客户端。

接下来使用 gRPC 模块创建 gRPC 服务器,并使用addService方法将服务方法添加到此服务中。最后将服务器绑定到端口并启动。

步骤 4:使用 Postman gRPC 客户端

现在,我们将编写服务方法逻辑 – 并使用 Postman gRPC 客户端来确认我们的方法按预期工作。

Postman gRPC 客户端的 UI

Postman gRPC 客户端可让您轻松测试、协作和使用 gRPC API。您可以上传 Protobuf 定义,Postman 将自动了解其所有服务和可用方法。它可以为每种方法生成示例有效负载,以及为整个服务生成模拟服务器。它支持服务器反射以及所有四种类型的数据流。

导航到工作台左侧边栏上的API选项卡,然后单击+图标以创建新 API。选择继续而不使用存储库,单击添加新定义文件,然后选择Protobuf 3。创建定义,复制并粘贴整个 Protobuf 文件,然后单击保存

注意:您还可以使用我们的编辑器直接在 Postman 中创作 Protobuf 文件 – 或者从远程存储库导入它。

接下来创建一个新的 gRPC 请求,并将此请求保存在一个新的集合中,如下所示:

第五步:编写服务方法逻辑

服务方法的函数已在 server.js 文件中定义。我们现在将为这些不同的方法编写逻辑。

创建产品

createProduct方法接收一个产品对象并将其推送到数据库数组。产品对象从中获取request.data。此数据被解构为包含产品 ID 的新对象。然后,将新对象推送到products数组并发送回客户端:

函数 createProduct(调用,回调){
const 数据 = 调用.请求;

const newProductData = {...数据,id:产品.length + 1};

产品.推送(新产品数据);

返回回调(null,newProductData);
}

添加此代码后,使用以下命令启动服务器:

节点服务器.js

导航到您的 Postman Collection 并创建一个名为“创建产品”的新 gRPC 请求。

将服务器 URL ( 0.0.0.0:4000) 复制并粘贴到服务器 URL 输入框中,然后单击选择方法。选择您在上一步中创建的 API 的名称。这应该会加载您的所有服务方法。选择createProduct方法。然后,在消息选项卡中,单击使用示例消息或手动添加产品消息。修改示例消息以满足您的需求,然后单击调用:

您将收到包含新创建产品数据的响应,该响应可保存为此请求的示例响应:

ReadProduct 和 ReadProducts

要读取单个产品,您将使用其 ID 在产品数组中搜索它。 如果数组中不存在具有指定 ID 的产品,则该函数将返回 gRPC 错误,并带有预定义状态代码之一:

函数 readProduct(调用,回调){
const productId = 调用.请求.id;
const selectedProduct = products.find(product => product.id === productId);

如果(selectedProduct){
返回回调(null,selectedProduct);
} 别的 {
打回来({
代码:gRPC.status.NOT_FOUND,
详细信息:“找不到具有指定 ID 的产品”
});
}
}

要读取所有产品,只需将产品数组返回给客户端:

函数 readProducts(调用,回调){
返回回调(null,{产品});
}

重新启动服务器,导航到您的集合,然后分别使用方法readProduct和创建两个新的 gRPC 请求readProducts。正如我们所解释的,该readProduct方法接受id作为其消息并仅返回指定的产品(如果该产品不存在则返回错误),而readProducts消息返回所有产品的数组:

更新产品

要更新产品,我们首先使用其 ID 在产品数组中找到它。然后,我们创建一个新对象,其中包含来自请求的更新产品信息。请求中未包含的任何字段都将保留其当前值。然后,这个新对象将替换产品数组中的现有对象:

函数更新产品(调用,回调){
const productInfo = 调用.请求;

const productIndex = products.findIndex(product => product.id === productInfo.id);


如果(!productIndex){
返回回调({
代码:gRPC.status.NOT_FOUND,
详细信息:“无法找到要更新的具有指定 ID 的产品”
});
}

const selectedProduct = 产品[productIndex];

const updatedProduct = {
id:selectedProduct.id,
名称:productInfo.name ??selectedProduct.name,
描述:productInfo.description??selectedProduct.description,
价格:productInfo.price??selectedProduct.price,
类别:productInfo.category??selectedProduct.category,
}

产品.拼接(产品索引, 1,更新产品);

返回回调(null,updatedProduct);
}

重启服务器,导航到您的收藏,然后添加UpdateProduct请求。提供您要更新的产品的 ID,以及您要更新的属性:

单击“调用”。然后,导航到该viewProducts方法并调用它以确认具有指定 ID 的产品确实已更新。

删除产品

要删除产品,我们将使用请求中的产品 ID 在产品数组中找到该产品的索引。然后我们将使用此索引从数组中删除产品对象:

函数 deleteProduct(调用,回调){
const productId = 调用.请求.id;
const productIndex = products.findIndex(product => product.id === productId);
如果(!productIndex){
返回回调({
代码:gRPC.status.NOT_FOUND,
详细信息:“无法找到要删除的具有指定 ID 的产品”
});
}

产品.拼接(产品索引,1);

返回回调(null,{deleted:true});
}

重新启动服务器,在集合中创建一个新的请求,并deleteProduct在特定的 ID 上调用该方法:

最后,调用该ReadProducts方法来确认具有指定 ID 的产品确实已被删除。

其他资源

我们在本教程中涵盖了很多内容。我们设置了一个 Node.js 项目,使用 Protobuf 创建了一个 gRPC 服务,并设置了一个基本服务器。然后,我们为 gRPC 服务编写了服务函数,并了解了 Postman gRPC 客户端如何帮助您更有效地使用 gRPC API。

#你可能也喜欢这些API文章!