All Articles

探讨gRPC的Node技术生态及实现工具

1. 前言

gRPC是谷歌出品的一个RPC库,从使用上来说,大厂的技术实力和维护保障还是比较有说服力的,因此选择这个技术投入生产的技术人还不少。gRPC作为一款跨语言的技术栈,各语言都有针对性的客户端实现,也包含了JavaScript。因此各语言各自的实现及技术生态都不尽相同。

本文的主旨是就现在gRPC的Node技术生态进行简单的介绍及实际工具选择进行分析。

2. gRPC生态技术点

首先简单看一下,如果要构成一个高自动化程度的gRPC技术生态需要哪些技术点:

  • A. 使用proto定义文件解决所有的消息及数据结构的定义(Protobuf官方支持)
  • B. 使用proto定义文件解决所有的RPC输入输出定义(Protobuf对gRPC的官方支持)
  • C. 使用proto定义文件解决所有的HTTP Gateway输入输出定义(官方不支持,需第三方实现)
  • D. 能根据proto定义输出对外的说明文档,含数据模型及API接口(官方不支持,需第三方实现)
  • E. 能根据proto定义产生静态JS源代码文件(官方支持,且也有不错的第三方实现)
  • F. 能根据proto定义产生JS源代码对应的TypeScript定义(官方不支持,需要第三方实现)

这样就做到了程序员只需要定义一份proto文件,然后所有的:

  • 数据模型代码
  • RPC接口代码
  • Gateway接口代码
  • 代码对应的TypeScript定义
  • 文档

都能够自动生成,然后程序员只需要专注在自己的业务代码上即可。

RPC生态中列出的几点需求点,现在主要有几个工具可以作为解决方案(暂且先不论文档和Gateway等功能)。下面主要过一下这几个工具。

3. grpc/grpc-node

grpc官方的支持库。文档极度匮乏,这个库其实是一个集合类型的东西,不算是真正意义上的很清晰的项目代码库。里面有意义的主要是两个:

  • grpc:
    • npm
    • JS源码
    • C语言Addone源码
    • Node的gRPC实现,其实是一个基于Node的C++ Addone,在Node下要用gRPC就一定要装这个库。但也正由于是Addone形式,导致很多需要深入查看的地方查到后面都发现是桥接代码,并没有真正的JS实现代码。在debug和深入研究的时候容易出现障碍,有时候不得不去看C++的源码。
  • grpc-tools:
    • npm
    • 源码
    • gRPC官方的工具,其实就是个代码生成插件,用在protoc命令行执行的时候

3.1 代码库拆分的问题

Node的gRPC代码之前是放在gRPC主库里面的,但在后面的版本中分离了出来,这里可能会有部分资料的分裂和资源难以查找的问题,因此这里单独列一个段落对这块进行描述。

这个库作为Node的gRPC主库单独分离出来是在grpc的1.7.0版本之后,查看:grpc release 1.7.0。之前两者是放在同一个git库里的。

所以在grpc/grpc-node这个库的release里你是找不到1.7.0之前的版本的。

但供Node使用的npm上的grpc安装包则一直是这一个库,没有改动,可以放心使用。查看历史版本可以使用命令:

npm view grpc time

部分老版本的grpc在npm上存在,但实际安装可能会有编译,可以参考这篇文档从源码开始安装。

3.2 生态功能点支持

功能 支持与否
消息及数据结构的定义
RPC输入输出定义
Gateway输入输出定义 X
说明文档生成 X
JS代码生成
TS代码生成 X

可以看到这套技术解决方案除了最核心的技术点之外,外围周边的东西都是不支持的。从现在官方对核心那块的研发支持的度上来说,就不难理解为什么很有用的外围技术点就更加没有支持了。

3.3 grpc

教程文档可以在这里找到。比较有价值的sample基本上都在代码文件的文件夹里。

$ git clone -b v1.9.x https://github.com/grpc/grpc

$ cd grpc
$ cd examples/node

从这里就可以看出来拆分还是不彻底,库和文档主要部分都拆开了,但示例代码居然仍旧在grpc库里。

3.4 grpc-tools

grpc-tools没有README入口文档。使用文档放在一个很幺二三的角落:grpc/examples/node/static_codegen/README.md

e.g

cd ../../protos    
npm install -g grpc-tools    
grpc_tools_node_protoc --js_out=import_style=commonjs,binary:../node/static_codegen/ --grpc_out=../node/static_codegen --plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin` helloworld.proto    
grpc_tools_node_protoc --js_out=import_style=commonjs,binary:../node/static_codegen/route_guide/ --grpc_out=../node/static_codegen/route_guide/ --plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin` route_guide.proto    

生成的代码范例如下:

3.5 技术问题

官方的这套grpc-node解决方案有几个问题还是比较麻烦的,最主要的有几点:

  • 使用grpc-tools工具生成出来的JS代码:
    • 语法上非常古老,还是上个世代的JS语法,实在不能说是很好的使用体验,和现在的现代化JS项目格格不入
    • 虽然是JS代码,但囿于grpc整体的设计,生成出来的代码语言特性上更接近Java和C++,而不是JS
  • 运行时使用的grpc的js代码
    • 语法上非常古老,还是上个世代的JS语法,实在不能说是很好的使用体验,和现在的现代化JS项目格格不入
    • 其实就是包裹在C++Addone上的一层桥接,在需要debug的时候各种看不懂,还需要去研究C++源码
  • 异步调用的处理:
    • 囿于grpc的消息处理设计,async/await、甚至退一步说 Promise,在grpc现在的JS代码中都是无法使用的
    • 早的有[Features] Node client support Promise进行过讨论,无疾而终
    • 近一点的也有[NodeJS] Promises and async/await进行过类似话题的讨论,但也没有结论。在这个帖子里,官方人员也解释了设计上的难点
    • 当然也有第三方的库尝试解决这个问题,bojand/grpc-caller就是一个,但始终也只是个玩具

主要的问题还是在于更新的支持力度上,代码版本过于古早,技术上需要发点力才能解决的异步处理等等,都没有推进。现在的状态就是,能用,但离理想状态还太遥远。特别是生成出来的JS代码以及grpc的JS代码还不支持TS定义(生成),在TS使用者眼里看来,简直完全等于不可用了。

不过好在新的纯JS实现的JS源码(研发中),让人对未来抱有期待。此外,1.7.0版本之后官方也带了grpc自身的JS的TS定义,总算是可以用了。我这边也研发了一个protoc的插件,帮助生成被生成出来的JS那份的TS定义,算是圆上了生态。

4. protobuf.js

这是一款第三方制作的开源Protobuf解析库,需要注意的是,这个库的偏向是面向Protobuf,它主要服务的是Protobuf的解析,以及对应protobuf的JS代码生成,RPC并不是它的主要方向。

资源:

教程文档直接看官方的README文档就差不多了,写得非常好,grpc官方的文档和它完全不能比。

需要注意的主要是用来代码生成的命令行工具的教程,官方文档:Command line

生成出来的代码和grpc-tools有很大差别,可以比对查看下:

生成出来的代码文件是一个大而全的compiled.js,此外int64类型转换成了它自己的一个子工具库dcodeIO/long.js需要注意。

4.1 生态功能点支持

功能 支持与否
消息及数据结构的定义
RPC输入输出定义
Gateway输入输出定义 X
说明文档生成 X
JS代码生成
TS代码生成

这套技术解决方案相比官方的grpc方案,多了一个TypeScript支持。且,其代码生成都是基于ES6最新的语言规范支持,对于最新的JS和TS开发者来说都非常友好。而且接口设计上也`更JS`:
  • 所有的对象 attributes 都是使用 getter 和 setter 进行访问的,而不是类Java那种的 setXXX、getXXX
  • 对象的创建可以直接使用create(object)的方式来进行,代码上简化不少,不会有grpc官方的 let xxx = new XXX;接着一串 xxx.setXXX

总而言之,无论是看还是写,都比官方的代码要好上不止一点。

虽然Gateway和文档的生成仍旧不支持,当然这是基于它的定位,这部分本来就不在它的涉足范围内。

4.2 技术问题

这里说的技术问题说实在的也不能说是问题,主要是基于当前Topic的gRPC这个基准而产生的使用上的问题。问题来自我介绍这个库时候一开始说的,它是面向Protobuf,而不是gRPC。因此在设计上,这个库给予使用者选择rpc框架的自由,代码中的rpc部分只给出了接口,实现部分需要自己处理。

因此如果你是一个gRPC的用户,根本不考虑其他的库的话,那么使用Protobuf.js需要额外付出劳力对这块进行整合。

官方的rpc使用教程在:Using services 官方的范例代码在:examples/streaming-rpc.js

可能是因为最新的版本出来时间还不久,关于如何在Protobuf.js里集成使用rpc的讨论非常非常少,简单搜了一圈,基本上没有官方的,或者三方的gRPC整合教程,如果要使用的话,可以预期会有大量的时间投入。这基本上能算是协同Protobuf.js组建gRPC生态的最大问题了。

这里还有一个设计相关的讨论帖:Streaming RPCs / GRPC Compatibility

5. grpc-gateway

这个项目隶属于一个名叫gRPC Ecosystem的官方群组。

gRPC Ecosystem that complements gRPC

总的来说算是一些补足gRPC生态的边角料项目的组合。

这个群组里看下来最有意义的应该说就是这个项目了,核心的设计和工作目标可以看这张图

简单来说就是可以根据protobuf的(扩展)定义,生成对应的:

  • swagger.json 配合swagger生态使用
  • 生成Gateway代码,注意这个项目是Go语言的,因此生成出来的代码也是Go语言的

用到了非标准的annotation,主要是在proto文件中引入了额外的官方proto定义:

import "google/api/annotations.proto";

这个引入的proto还引入了两个其他的proto,总共3个proto文件,如下:

作为Node来说,主要能拿到生成出来的swagger文件,后面的gateway代码生成也就简单了。

5.1 生态功能点支持

功能 支持与否
消息及数据结构的定义 X
RPC输入输出定义 X
Gateway输入输出定义
说明文档生成 X
JS代码生成 X
TS代码生成 X

简单来说这套工具主要能弥补之前提到的两套工具的Gateway方面短板,补全整个生态。

6. 结论

从目前来看,用Node.js和TypeScript来构建gRPC生态架构还是非常乐观的:

功能 支持与否
消息及数据结构的定义
RPC输入输出定义
Gateway输入输出定义
说明文档生成 X
JS代码生成
TS代码生成

除了文档的生成之外,其他的功能点基本上都有工具可以覆盖到。此外官方的TypeScript定义也已经正式上线,并在积极更新,可以期待后续的发展。

技术选型:

  • 如果对于自己的技术团队有自信,并有时间和精力能适配Protobuf.js的rpc实现的话,那么Protobuf.js应该说是一个更现代化、更新更高速、更好的选择
  • 如果对于时间和精力没有余裕,更愿意相信官方的话,那么grpc官方的工具套件可以说是一个虽然读写不是很完美,但功能完备更有稳定性保障的选择

总而言之,如何选择端看需求和自身的条件情况。

7. 其他

其他还有一些工具也能起到补助作用这里简单列下:

  • ts-protoc-gen:Protoc Plugin for TypeScript Declarations
  • grpc_tools_node_protoc_ts:Generate corresponding TypeScript d.ts codes according to js codes generated by grpc_tools_node_protoc
  • grpc-tsd:Maintain a grpc.d.ts file for gRPC TypeScript project.

8. 资料

EOF

Published 2018/2/6

Some tech & personal blog posts