需求背景是这样的
在 GraphQL (Apollo Server)服务中对接 gRPC 服务,对接服务中包含多个在不同目录层级的 proto 文件,并且存在互相引用,以及对 google proto文件的引用。同时,在开发过程中 Apollo Server 会使用 Mock 模式,在使用 Mock 模式时不希望初始化 gRPC 客户端实例,因为本地没有 gRPC 服务。
抽象后的要求是
- 支持 动态引用 proto文件
- 支持 proto 的 import 语法
- 支持 proto 的 package 语法
- 支持指定 proto 的引入目录
- 支持直到使用时才创建实例的 延迟初始化
const grpc = require("@grpc/grpc-js");
const loader = require("@grpc/proto-loader");
const path = require("path");
function client(filename, package, service) {
const definition = loader.loadSync(`${filename}`, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
// CAUTION: proto files import path relative from 2 places
includeDirs: [
path.join(__dirname, "./current/path"),
path.join(__dirname, "./path/to/proto/root"),
path.join(__dirname, "./path/to/other/extra"),
],
});
const proto = grpc.loadPackageDefinition(definition);
let Client = proto;
for (let i of [...package.split("."), service]) {
if (i) Client = Client[i];
}
return new Client(
process.env.GRPC_PEDESTAL_URI,
grpc.credentials.createInsecure()
);
}
let _clients = {};
function proxy(file, package, service) {
if (!file || !package)
throw Error("Target must contain file and service field");
function getCachedlient() {
const K = file + package;
if (!_clients[K]) _clients[K] = client(file, package, service);
return _clients[K];
}
const handler = {
get: function (target, prop, receiver) {
let client = getCachedlient();
return function () {
let [params, callback] = arguments;
if (callback) {
client[prop](...arguments);
} else {
return new Promise((resolve, reject) => {
client[prop](params, (err, res) => {
if (err) reject(err);
else resolve(res);
});
});
}
};
},
};
return new Proxy({ file, package, service }, handler);
}
const clients = {
ClientA: proxy(
"path/to/a.proto",
"EntryService"
),
ClientB: proxy(
"path/to/some_within_a_package.proto",
"com.company.project.demo.v1",
"EntryService"
),
};
module.exports = clients;
注意事项:
- 加载 proto 文件的 includeDirs 要与 proto 文件的保存目录匹配
- proto 的 package 需要手动指定,否则无法使用
- 延迟加载的机制,即使是在变量被引用后,也不会初始化,直到真正被当做方法使用时才会初始化
参考内容
网友评论