- Author(s): murgatroid99
- Approver: wenbozhu
- Status: Ready for Implementation
- Implemented in: Node.js
- Last updated: 2019-05-15
- Discussion at: https://groups.google.com/forum/#!topic/grpc-io/t7XFE7Wevow
This proposes an API for the new standalone gRPC+Protobuf.js library that we will introduce.
The Node gRPC library currently depends on Protobuf.js version 5. Since Protobuf.js 6 was introduced, Protobuf.js 5 has not been maintained. In the past we have tried to upgrade that dependency in place, but that caused breaking changes that we did not accept. Our solution to this problem is to publish a new library that is solely responsible for loading .proto
files using Protobuf.js and transforming them into a generic format that gRPC can use. The introduction of this new library provides an opportunity to improve the API for loading files based on feedback on the existing library.
For the purpose of definiing services in general, the existing gRPC library has defined the types MethodDefinition
and ServiceDefinition
. A MethodDefinition
is an object with the following properties:
path
: The URL path for accessing this methodrequestStream
: A boolean indicating whether there is a stream of request messages instead of a single message.responseStreams
: A boolean indicating whether there is a stream of response messages instead of a single message.requestSerialize
: A function for serializing a request object to aBuffer
responseSerialize
: A function for serializing a response object to aBuffer
requestDeserialize
: A function for deserializing a request object from aBuffer
responseDeserialize
: A function for deserializing a response object from aBuffer
This information is sufficient to implement a client or service handler for any kind of method. Then a ServiceDefintion
is just an object that maps method names to MethodDefinition
objects. When loading a .proto
file, more than one service may be loaded, so we additionally define a wrapping type PackageDefinition
, which maps fully qualified service names to ServiceDefinition
objects. For example, if a .proto
file defines the package a.b
with the services Service1
and Service2
, the resulting PackageDefinition
would have the keys "a.b.Service1"
and "a.b.Service2"
.
The library exposes the following function:
/**
* Asynchronously load a .proto file and all of its transitive imports
* @param {string|string[]} filename File path to the .proto file(s) to load.
* @param {Object} options Options for loading the file and setting up the deserializers
* @return {Promise<PackageDefinition>} A promise for an object containing definitions for all of the loaded services
*/
load(filename, options)
/**
* Synchronously load a .proto file and all of its transitive imports
* @param {string|string[]} filename File path to the .proto file(s) to load.
* @param {Object} options Options for loading the file and setting up the deserializers
* @return {PackageDefinition} A promise for an object containing definitions for all of the loaded services
*/
loadSync(filename, options)
The filename
parameter can be either a string or an array of strings. If it's a string, it contains a path to a .proto
file to load. If it's an array of strings, each element is a path to a .proto
file to load, and the resulting PackageDefinition
will contain services and methods from all .proto
files in the array. This is the same behavior as in Protobuf.js's Root.load
.
The options
parameter of those functions is the union of Protobuf.js's IParseOptions
(which modifies how files are loaded) and IConversionOptions
(which modifies deserialization functions), plus an include
option to specify directories to search for dependencies. The full set of options is as follows:
keepCase
: a boolean indicating that field names should be preserved. The default is to change them to camel case.longs
: Can be set toNumber
orString
to indicate that long values should be represented as that type. The default isNumber
, or a safer objectLong
type if the library is installed.enums
: Can be set toString
have enums represented as strings. The default is to use their numeric value.bytes
: Can be set toArray
orString
to indicate that bytes values should be represented as that type. The default is to use the Node built in typeBuffer
.defaults
: a boolean indicating that default values should be set on output objects.arrays
: a boolean indicating that empty arrays should be set for missing array values even ifdefaults
isfalse
.objects
: a boolean indicating that empty objects should be set for missing object values even ifdefaults
isfalse
.oneofs
: a boolean indicating that virtual oneof properties should be set to the present field's name.json
: a boolean indicating that additional JSON compatibility conversions should be performed.includeDirs
: an array of strings listing paths in which to search for imported.proto
files.
This package provides very similar functionality to the existing grpc.load
API, but with some differences. First, this API directly exposes all of the relevant Protobuf.js options instead of defining a subset of custom options. This package is explicitly a wrapper around Protobuf.js, so it makes more sense here to directly and consistently expose the relevant options from that library. Second, this API does not provide an equivalent to the loadObject
function in the existing gRPC library. With the Protobuf.js options directly exposed, there is less benefit in allowing the user to separately load .proto
files. Third, this adds an asynchronous API for loading .proto
files. This has been requested before, and more closely matches existing Node APIs. It uses promises instead of callbacks because they are more versatile, and because Node is increasingly supporting them.
I (murgatroid99) will implement this during Q1 2018.