如何在grpc中使用nacos异步配置连接地址

566 阅读4分钟

前言

知道之后其实是个很简单的写法,但在实现过程中确实遇到了不小的麻烦,几乎花费了我两天的时间来解决这个问题,期间甚至去查看了nest依赖注入部分的源码,尝试了非常多不同风格的写法均没能达到想要的效果,最后在一次报错信息中获取灵感找到了最终的解决办法。

如何处理

懒得看我的解决过程的同学可以只看这一部分,在这里我默认你已经知道如何配置nacos了,如果有不知道的同学可以参考我之前的这篇文章:Nestjs整合nacos作为配置中心

定义grpc的连接类在构造函数中获取nacos配置写入

  
  client: ClientGrpc

  constructor(private readonly nacosConfig: ConfigService) {
    const option = userRpcPath(nacosConfig.get("USER_SERVER_URL"));
    this.client = ClientProxyFactory.create(option);
  }

附上userRpcPath的实现

export const userRpcPath = (path: string) => {
    return {
        transport: 4,
        options: {
            url: path,
            package: "user",
            protoPath: join("src/rpc", "user.proto")
        }
    }
}

关键步骤在于ClientProxyFactory.create方法的使用,可以利用连接的代理工厂生成一个连接对象,这种写法和@Client()装饰器的属性写法是等价的。

好了,解决方案到这里就结束了,下面是我在解决这个问题过程中的一些思路和遇到的问题,有兴趣的同学可以接着往下看。

历史方案

最早我是期望通过@Client注解直接将grpc连接注入到client对象的,但是又不想把微服务地址在代码里写死,这样一来不论是更换环境还是迁移服务都可能造成地址不对连接不上微服务的情况,也有悖于之前使用nacos的初衷,所以还是得使用nacos来配置微服务地址。

那么如何去获取这个配置就成了问题。

因为nacos的配置是异步获取的,直接在装饰器上是不能使用await语法的 @Client(await getConfig())这种语法在编译器会直接报错,于是需要通过其他方式来获取这个异步配置。

在nest属性注入的方法,有一个常用的@Inject()装饰器,这个装饰器可以通过别名的方式来注入属性,如:@Inject('USER_SERVER_URL'),其中USER_SERVER_URL定义的地方在对应Moduleproviders数组,基本写法:

    {
      provide: 'USER_SERVER_OPTION',
      useValue: "hello"
    }

通过这种方式可以将值hello注入到装饰器对应的属性里,但是这个方式也不能使用异步,这时候就需要另一个参数:useFactory

    {
      provide: 'USER_SERVER_OPTION',
      useFactory: async () => {
        return await userRpcPath("USER_SERVER_URL");
      }
    }

这种写法支持异步调用方法,但是依然有一个问题,就是我虽然拿到了参数,但无法将参数填写到@Client()里去,因为如果使用注入的方式去调用,由于对象注入是先注入属性再生成对象,在注入属性之前是没有办法拿到this值的。

image.png

那么使用静态变量又如何

image.png

这一次编译器倒是没报错,但在实际运行的时候提示错误:

TypeError: Cannot read properties of undefined (reading 'customClass')

image.png

这可能是我在这次解决问题期间遇到最多的问题了,大致意思就是,你传过来的值我收到了,但这是个空值,也就是说,在初始化client时,这个静态对象还没初始化完成。为了将这个配置类提前到client之前初始化,我可以说是吃尽了苦头,尝试过以各种方式放进构造器,甚至尝试过新建一个模块,将模块中的服务引入当前服务来提高注入的优先级,但都以失败告终,失败的写法在这里就不赘述了。

不知道上面那个报错出现了多少次,我突然意识到,之前查看异常的时候只看了报错部分是由于空指针,但其他实现逻辑会不会也在这文件一起。因为我注意到这个文件的名字叫

client-proxy-factory——连接代理工厂。

进入到代理工厂类,可以看到下面的方法是报错部分,而上面的方法就是创建示例,突然感觉要被自己蠢哭了。

image.png

如果不是这个报错,还不知道要找这个类多久;

如果不是创建方法和判断方法写在了同一个文件下,还不知道要找这个类多久。

期间也想过不通过装饰器的方式直接手动创建连接,但不论是查文档还是问同事都没有使用过类似的方法,搜索nest grpc关键词,连接的方式一直都只有通过@Client()一种方式,不知道是我搜索关键词有问题还是这是一种不推荐的方法,这真的让人感觉挺痛苦的。

一遍一遍的更换关键词,一遍一遍的搜出同样的不是我想要的答案,在痛苦和失望中寻找光明,可能这就是人生吧。