Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/kaelzhang/gaia
Gaia, the framework to make gRPC services
https://github.com/kaelzhang/gaia
grpc nodejs server
Last synced: 3 months ago
JSON representation
Gaia, the framework to make gRPC services
- Host: GitHub
- URL: https://github.com/kaelzhang/gaia
- Owner: kaelzhang
- License: mit
- Created: 2018-06-14T08:59:20.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2020-10-03T09:52:02.000Z (over 4 years ago)
- Last Synced: 2024-04-15T12:32:58.444Z (9 months ago)
- Topics: grpc, nodejs, server
- Language: JavaScript
- Homepage:
- Size: 170 KB
- Stars: 3
- Watchers: 3
- Forks: 2
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[![Build Status](https://travis-ci.org/kaelzhang/gaia.svg?branch=master)](https://travis-ci.org/kaelzhang/gaia)
[![Coverage](https://codecov.io/gh/kaelzhang/gaia/branch/master/graph/badge.svg)](https://codecov.io/gh/kaelzhang/gaia)# gaia (for node.js)
Gaia, the very framework to make [gRPC](https://grpc.io) services. Gaia defines a definitely intuitive way to write gRPC services.
- **Handle Custom Errors** `gRPC` does NOT provide an formal way to handle errors, even lack of documentation, while `gaia` will do it for you.
- **Manage `.proto` files** `gaia` allows us to share proto files between server and clients. `gaia` shares `gPRC` protobuf files by wrapping them into an npm package and publishing the npm tarball to npm registry.
- **Eggjs compatible plugins** `gaia` supports to use [egg plugins](https://github.com/search?q=topic%3Aegg-plugin&type=Repositories) to extend your applications.
- **Restful API service made easy** `gaia` provides a convenient way to define restful API routings upon the existing gRPC services.`gaia` supports both [**proto2**](https://developers.google.com/protocol-buffers/docs/proto) and [**proto3**](https://developers.google.com/protocol-buffers/docs/proto3).
## Install
```sh
$ npm i gaia
```## Table of Contents
- [APIs](#apis)
- [Client](#new-clientroot-clientconfig)
- [Server](#new-serverroot-serverconfig)
- [How gaia makes `.proto` files sharable and portable?](#how-gaia-makes-proto-files-sharable-and-portable)
- [Create the client of `hello`](#create-the-client-of-hello)
- [Import `.proto` files from `hello`](#import-proto-files-from-hello)
- [More about `includeDirs`](#more-about-includedirs)
- [How to Write a `gaia` Server](#how-to-write-a-gaia-server)
- [Packages and name resolution](#packages-and-name-resolution)
- [`this` object of the controller methods](#this-object-of-the-controller-methods)
- [Reusing other controllers](#reusing-other-controllers)
- [Using external services](#using-external-services)
- [Using plugins](#using-plugins)## Synopsis
```js
const {
Server,
Client,
resolvePackage
} = require('gaia')const root = path.join(__dirname, 'example', 'hello')
```To make better understanding the usage of `gaia`, **the example below is based on the demo in the
[`example/node/hello`](https://github.com/kaelzhang/gaia/tree/master/example/node/hello) directory**.Start server:
```js
new Server(root).listen(50051)
```Run client:
```js
const {
// service Greeter
Greeter
} = new Client(root).connect('localhost:50051')const run = async () => {
const {message} = await Greeter.sayHello({name: 'world'})console.log(message)
}run()
// Hello world
```# APIs
## new Client(root)
Creates the gaia client.
- **root** `path` the root path to load the client from
### client.connect(host):
Connects to the gRPC server and returns the service methods
- **host** `string` the server host to connect to which includes the server hostname and port and whose pattern is `:`
## new Server(root, serverConfig?)
- **root** `path` the root path to load the server from
- **serverConfig?** `ServerConfig={}` server configurations```ts
interface ServerConfig {
// Defines where to load controllers
controller_root?: string = 'controller'
plugins?: Array
services?: Services
}interface Package {
// The root path of the package
path?: string
// The package name of the package
package?: string// Either path or package should be defined.
}interface Plugin extends Package {
// Configurations for the plugin
config: object
}interface Service extends Package {
// the host param of `client.connect(host)`
host: string
}interface Services {
[name: string]: Service
}
```### server.listen(port): this
- **port** `number` the port which gRPC server will listen to.
Start the gaia server.
### server.kill()
Forcibly shut down the gRPC server
### await server.close()
Gracefully shut down the server
## resolvePackage(id: string): string
- **id** `string` package id
Returns the root path of the package
```js
new Client(resolvePackage('foo')).connect(host)
```## How `gaia` makes `.proto` files sharable and portable?
`gaia` takes full advantage of npm packages to share proto files.
A minimun `gaia` service portable, as well as service `hello` or package `hello`, could be:
```
/path/to/hello/
|-- proto/
| |-- hello.proto
|-- package.json
```And in `proto/hello.proto`:
```protobuf
syntax = "proto3";service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}message HelloRequest {
string name = 1;
}message HelloReply {
string message = 1;
}
```package.json
```js
{
"name": "hello",
"gaia": {
...
}
}
```The the **optional** field `"gaia"` of package.json follows the schema:
```ts
interface FieldGaia {
// Tells `gaia` which properties of error should be
// - collected, serialized and transmitted to the clients.
// - or deseriialized from server
// `errorProps` defaults to `['code', 'message']`// if the server throws an `error`, by default, gaia will collect
// - `error.code`,
// - `error.message`
// and send them to its clients, while other properties will be omitted.
errorProps?: Array = ['code', 'message']
// Specifies where to load proto files.
// `protoPath` should be a relative path to `root`
protoPath?: string = 'proto'
// Proto filenames inside `protoPath`.
// If not specified, gaia will search all `*.proto` files inside `protoPath`.
protos?: Array | string = '*.proto'// See section #import-proto-files-from-hello below
protoDependencies?: Array = []
}
```Apparently, package `hello` has everything we need to create a client agent for service `hello`.
And package `hello` is language-independent which only contains proto files and client configurations.
### Create the client of `hello`
Assume that we have a new project `foo`, and we `npm install hello`.
```
/path/to/foo/
|-- proto/
| |-- foo.proto
|-- node_modules/
| |-- hello/
|-- package.json
```Then if the `hello` service is already running on port `8000`, we could create a hello client by following lines:
```js
const {Client} = require('gaia')
const {Greeter} = new Client('/path/to/foo/node_modules/hello').connect('localhost:8000')
```### Import `.proto` files from `hello`
Since project `foo`, as we introduced above, has a dependency `hello`, we could import `.proto` files from package `hello`.
in `/path/to/foo/proto/foo.proto`:
```protobuf
syntax = "proto3";// We could install a package and import things from it
// as well as we do in JavaScript es modules. Oh yeah! 😆
import "hello/proto/hello.proto"service FooGreeter {
// We could reuse message types from package `hello`
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
```In order to do that, we need to declare that `hello` is a `gaia` dependency of `foo` by adding some fields in package.json:
```js
{
"name": "foo",
"gaia": {
// So that we could import .proto files from package `hello`
"protoDependencies": [
// We have to add "hello" here.
"hello"
]
},
"dependencies": {
// This is generated by `npm install`
"hello": "^1.0.0"
}
}
```And `gaia` will manage the [`--proto_path`](https://developers.google.com/protocol-buffers/docs/proto3#importing-definitions)s ([includeDirs](https://www.npmjs.com/package/@grpc/proto-loader)) for you, so that gRPC Protobuf Loader will know where to search and import `.proto` files
### More about `includeDirs`
`gaia` recursively parses the `protoDependencies` of project `foo`, and its `protoDependency`'s `protoDependencies` to generate the `options.includeDirs` option for [`@grpc/proto-loader`](https://www.npmjs.com/package/@grpc/proto-loader)
## How to Write a `gaia` Server
Take the project `hello` which introduced above for example.
Since we define a `Greeter` service in `hello.proto`, we must implement the corresponding controller by ourselves.
Service controllers should be defined in directory `/path/to/hello/controller` which can be changed with by config `controller_root`.
We must provide a `Greeter.js` in that directory.
```
/path/to/hello/
|-- controller/
| |-- Greeter.js
```in [`Greeter.js`](example/hello/controller/Greeter.js), there should be an async/sync method named `SayHello` in `exports` because we defined a `SayHello` rpc method in service `Greeter`
```js
exports.sayHello = ({name}) => ({
message: `Hello ${name}`
})
```### Packages and name resolution
First the innermost package scope is searched, then the next-innermost, and so on, and at last the service name.
Assume that we have the following protocol buffer.
```proto
package foo.bar;service Baz {
rpc Quux (Req) returns (Res) {}
}
```Then in directory `controller_root`, we need to create a JavaScript file `foo/bar/Baz.js` whose `exports` has a `Quux` method.
### `this` object of the controller methods
There are several properties could be access by `this` object of the controller methods.
#### Reusing other controllers
We could access other controller methods by
```js
this.controller[namespace0][namespace1]...[serviceName][methodName]
```For example, we could access the `Quux` method by
```js
exports.OtherMethodsOfSomeService = async function (request) {
const data = await this.controller.foo.bar.Baz.Quux(request)
// ...
return something
}
```#### Using external services
If we provide `serverConfig.services` for server
```js
new Server('/path/to/service/foo', {
...otherConfig,
services: {
hello: {
// 'hello' is a gaia server
package: 'hello'
}
}
})
.listen(port)
```Then, client of the service `'hello'` could be accessed from the service controller of foo by:
```js
exports.Quux = async function ({name}) {
const {message} = await this.service.hello.SayHello({name})
return {
property: message
}
}
```#### Using plugins
## License
[MIT](LICENSE)