@plmtest/cosmos-service-builder
v1.0.26
Published
Package that exposes class instances to facilitate service creation
Readme
PLM Fleet Service Builder
This package provides abstractions for creating micro-services.
It bundles common utilities and middlewares for web services serving REST and/or gRPC APIs.
Usage
Starting an Express Server
const { ExpressServer } = require('@plmtest/cosmos-service-builder')
new ExpressServer({
configuration: {
port: config.APP_PORT,
adminPort: config.ADMIN_PORT
},
logger: config.LoggerConfig
})
.start()
.addServerMetrics(config.MonitoringConfig)
.addProbes({ readinessProbe, livenessProbe })
.asWebService()
.withSecuredEndpoints({
security: {
jwksUri: config.JWKS_URI
}
})
.addHeaderMiddleware()
.addRequestHandlerMiddleware()
.addRoutes('v1', routes, { remotes, services })
.addResponseHandlerMiddleware()
.stop()This starts a express server with the following:
- The server exposes two ports:
config.APP_PORTwhich is the application port providing all application endpoints, typically port 8080config.ADMIN_PORTwhich is the admin port providing additional endpoints like/metrics,/readzor/healthz, typically port 8081
- The service will provide a
/metricsendpoint that exposes Prometheus metrics for monitoring purposes. - The functions
readinessProbeandlivenessProbethat are passed intoaddProbesare executed when the probe endpoints/healthzand/readzare called. There are typically configured as endpoints for Kubernetes liveness and readiness probes. - Requests to all endpoints of the service requre a valid OAuth access token (JWT). It will be checked using
config.JWKS_URI. - All routes from
routesare exposed as endpoints with the prefixv1.routesis expected to be a map of router functions. The map's Keys will be used as context paths. - The service will gracefully shut down once it receives any KILL signal.
Starting a gRPC Server
const { GRPCServer } = require('@plmtest/cosmos-service-builder')
new GRPCServer({
configuration: {
port: config.GRPC_PORT,
host: config.GRPC_HOST
},
logger: config.LoggerConfig
})
.addPackages({ protoPaths }, { servicePackages, params: { remotes, services } })
.start()
.stop()This starts a gRPC server on port GRPC_PORT at host GRPC_HOST, typically 0.0.0.0:50051.
Adding gRPC Services from Protobuf
Before the server is started using start(), services or packages need to be added to the server.
They originate from protobuf definition and refer to implementation in the service.
Three different ways to add services are available:
addService()addPackage()addPackages()
All three functions take two parameter object arguments. The first one refers to the protobuf, while the second refers to the service implementation(s) and arguments to be passed into the constructor.
addService() - Adding an individual service
Using addService() offers the most flexibility adding a service to the gRPC server as almost everything is explicitly defined.
Nevertheless, it is recommended to follow the conventions mentioned below.
addService({
protoPackage: 'sample.hello',
protoService: 'HelloService',
protoPath: protoPaths.sample // imported from a protobuf package
}, {
serviceClass: servicePackages.sample.hello.HelloService, // class of the service implementation
params: { remotes, models }
})protoPackageandprotoServiceshould match what is defined in the reference protobuf file underprotoPath.serviceClassis a reference to the constructor of the grPC service implementation, which will be instantiated usingparams.- As an alternative to
serviceClassa service instance can be passed in asservice.paramswill be ignored in this case.
addPackage() - Adding an entire package
This is a convenience function that adds all services from a protobuf file and package to the gRPC server. In order to make this work as expected, it is important to follow the conventiones described below.
addPackage({
protoPath: protoPaths.sample // imported from a protobuf package
}, {
servicePackage: servicePackages.sample, // map of the service class implementations
params: { remotes, models }
})servicePackageis a (nested) map of packages and service class implementations. Sub-packages defined in the protobuf file are expected to exist as paths in that map.
addPackages() - Adding all defined packages
This is another convenience function that adds all packages that match the provided protobuf files to the gRPC server. In order to make this work as expected, it is important to follow the conventiones described below.
addPackages({
protoPaths // imported from a protobuf package
}, {
servicePackages, // map of the service class implementations
params: { remotes, models }
})Internally, this function relies on addPackage().
Implementing gRPC Services
The implementation of gRPC services relies on a set of assumptions or conventions.
A gRPC service:
- is implemented as a class with a single-argument constructor
- implements all functions exactly as they are defined in the associated protobuf file
- has the same name as the service defined in the associated protobuf file
- is located in a directory that matches the protobuf package, e.g.,
sample.hello.HelloServiceis implemented insample/hello/HelloService.js
Moreover, protobuf packages are expected to export a map of proto paths, e.g.:
const baseProtoUrl = `${__dirname}/protos`
module.exports = {
sample: `${baseProtoUrl}/sample.proto`
}Furthermore, the key (sample) is expected to match the name of the root package.
Setting up a gRPC Client
const { GRPCClient } = require('@plmtest/cosmos-service-builder')
const protoPaths = require('@plmtest/sample-protobufs')
const grpcClients = {
...new GRPCClient({
protoPath: protoPaths.sample,
logger: config.LoggerConfig
}).connect('127.0.0.1:50051') // we connect to ourselves
}This adds gRPC clients to the grpcClients object.
Packages and services defined in the protobuf file will be available pased on there package structure.
The service defined in the protobuf below can be called as follows:
package sample.hello;
service HelloService {
rpc sayHello (Empty) returns (SayHelloResponse) {
}
}const someHelloFunction = async () => {
const response = await grcClients.sample.hello.HelloService.sayHelloAsync({})
}Note that the gRPC client will use bluebird to promisify the gRPC service methods. So it will return a promise-based gRPC service call instead of callbacks.
As you can see in the example, this will append 'Async' to all the corresponding
proto methods, i.e, sayHello on the proto definition becomes sayHelloAsync
when you want to use the gRPC client to call that service.
