Skip to content

Commit

Permalink
L107: first draft of node gRPC reflection library proposal
Browse files Browse the repository at this point in the history
  • Loading branch information
jtimmons committed Oct 10, 2023
1 parent ee75a40 commit 40983fc
Showing 1 changed file with 115 additions and 0 deletions.
115 changes: 115 additions & 0 deletions L107-node-grpc-reflection-library.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
Node.js Reflection Server Library
----
* Author(s): jtimmons
* Approver:
* Status: Draft
* Implemented in: Node.js
* Last updated: 2023-10-09
* Discussion at:

## Abstract

Create a canonical implementation of the gRPC reflection API for Node.js based on the logic from the [nestjs-grpc-reflection library](https://gitlab.com/jtimmons/nestjs-grpc-reflection-module/-/blob/30b67a78ff99e31ae54a0ab34c3784316579c665/src/grpc-reflection/grpc-reflection.service.ts)

## Background

Since its introduction in 2017 there have been a variety of external node.js implementations for the [gRPC Reflection Specification](https://github.com/grpc/grpc/blob/ce75ec23a1a9c5239834b92da4ce0992d367a39c/doc/server-reflection.md), each of which is in various states of maintenance. A few examples are linked at the bottom of this section.

This feature was initially requested in grpc/grpc-node#79

* https://gitlab.com/jtimmons/nestjs-grpc-reflection-module
* https://github.com/deeplay-io/nice-grpc/tree/master/packages/nice-grpc-server-reflection
* https://github.com/AckeeCZ/grpc-server-reflection

### Related Proposals:
* Initial reflection proposal: https://github.com/grpc/proposal/blob/master/A15-promote-reflection.md
* Proposal of API for similar library: https://github.com/grpc/proposal/blob/master/L106-node-heath-check-library.md

## Proposal

We are proposing the creation of a new `@grpc/reflection-server` package with the following external interface:

```ts
import type { Server as GrpcServer } from '@grpc/grpc-js';
import type { Options as ProtoLoaderOptions } from '@grpc/proto-loader';

type MinimalGrpcServer = Pick<GrpcServer, 'addService'>;

interface ReflectionOptions {
filename: string | string[];
loader: ProtoLoaderOptions;
}

export interface ReflectionServer {
constructor(options?: ReflectionOptions);
load(options: ReflectionOptions);
loadSync(options: ReflectionOptions);

addToServer(server: MinimalGrpcServer);
}
```

this class will be responsible for managing requests for the various published versions of the gRPC Reflection Specification. At the time of writing, this includes `v1` and `v1alpha` but may include more in the future. These version-specific handlers will be isolated into their own services, such as the following which can be used for both `v1` and `v1alpha` due to their identical interface:

**reflection.v1.ts**
```ts
import {
ExtensionNumberResponse,
FileDescriptorResponse,
ListServiceResponse,
} from './proto/grpc/reflection/v1/reflection';

export interface ReflectionV1Implementation {
async load(options: ReflectionOptions);
loadSync(options: ReflectionOptions);

listServices(listServices: string): ListServiceResponse;
fileContainingSymbol(symbol: string): FileDescriptorResponse;
fileByFilename(filename: string): FileDescriptorResponse;
fileContainingExtension(symbol: string, field: number): FileDescriptorResponse;
allExtensionNumbersOfType(symbol: string): ExtensionNumberResponse;
}
```

### Usage
The user will leverage the library in a way very similar to the [gRPC health check service](https://github.com/grpc/grpc-node/tree/83743646cf69baf9ae1294015de5ffed33339154/packages/grpc-health-check) by creating a new class to manage the reflection logic and then adding that to the gRPC server:

```ts
import { ReflectionServer } from 'grpc-health-check';

// load synchronously with the files we have right now
const reflection = new ReflectionServer({
filename: ['./proto/example.proto'],
loader: {
includeDirs: ['./node_modules/dependency/proto']
}
});
reflection.addToServer(server);
server.start();

// can reload later on if additional services are added
reflection.load({ ... })
```

## Rationale

### 1. Design Decision: use of proto-loader over protoc
several reflection implementations linked above leverage protoc in order to generate a representation of the proto schema to expose on the API. In this document we propose the use of proto-loader to inspect the schema at runtime instead in order to simplify the developer experience and be consistent with the [design of the `grpc-health-check` library](https://github.com/grpc/proposal/blob/ee75a4010214ddda02ba992e69f1c57be7f71497/L106-node-heath-check-library.md#switch-from-protoc-to-grpcproto-loader).

### 2. Design Decision: support multiple reflection implementations
currently not all reflection clients request the `v1` version of the spec so we need to include handlers for both `v1` and `v1alpha` to support both during the transition. For this reason we separate the reflection handling logic itself to allow for reuse across multiple service versions

## Implementation

I (jtimmons) will implement this once the maintainers have approved

## Open issues (if applicable)

### 1. Package Publishing

Not sure how the package versioning and publishing works but may need some help from a maintainer to set that up and publish things into the `@grpc` scope

### 2. keepCase Option
One of the more awkward pieces in here is dealing with grpc-js/proto-loader's `keepCase` option, though this isn't unique to this library. Consumers can have their gRPC server configured with `keepCase` either on or off which changes the message handling substantially: for example if enabled then we would need to refer to the `listServices` field as `list_services` instead.

I'm not sure if there's an official way of handling this but in the existing implementation we've had to introduce some [kludgey manual camel-casing logic](https://gitlab.com/jtimmons/nestjs-grpc-reflection-module/-/blob/30b67a78ff99e31ae54a0ab34c3784316579c665/src/grpc-reflection/controllers/v1.base.controller.ts#L48) to handle users of both settings.

0 comments on commit 40983fc

Please sign in to comment.