JavaScript对象延迟初始化,动态生成 gRPC 调用实例

2020-10-26  本文已影响0人  _delong

需求背景是这样的

在 GraphQL (Apollo Server)服务中对接 gRPC 服务,对接服务中包含多个在不同目录层级的 proto 文件,并且存在互相引用,以及对 google proto文件的引用。同时,在开发过程中 Apollo Server 会使用 Mock 模式,在使用 Mock 模式时不希望初始化 gRPC 客户端实例,因为本地没有 gRPC 服务。

抽象后的要求是

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;

注意事项:

  1. 加载 proto 文件的 includeDirs 要与 proto 文件的保存目录匹配
  2. proto 的 package 需要手动指定,否则无法使用
  3. 延迟加载的机制,即使是在变量被引用后,也不会初始化,直到真正被当做方法使用时才会初始化

参考内容

https://javascript.info/proxy

上一篇 下一篇

猜你喜欢

热点阅读