The cluster discovery module enables you to write your applications in a microservice-oriented way. It provides service discovery and communication. It was specially designed to work with thorin apps. This plugin acts as the client that communicates with a discovery registry. sconfig.io offers deep integration with their self-hosted discovery service, at discovery.sconfig.io.
You can also choose to host your own discovery gateway, by setting up your own registry from the git repo and direct your clients to work with it.
#install the plugin npm i --save thorin-plugin-discovery@1.x
'use strict'; //app.js entry file const thorin = require('thorin'); thorin.addPlugin(require('thorin-plugin-discovery')); // <-- add this line thorin.run((err) => {});
The discovery registry is an open source node.js application that handles your cluster's discovery and feeds information about the microservices it works with. You can setup your own self-hosted registry by visiting the official repo. All you need is redis and node.js > 4.x. We suggest using nginx as the reverse proxy and TLS termination handler.
Once you've setup your registry, just override the default gateway configuration to your registry's /dispatch full URL. and you're done!
thorin.persist
, so that in the event of a failure in the registry, you still have the latest representation of your cluster to work with.
Microservice configuration
The service.host possible values
internal
- use the first private IPv4 from 10.0.0.0/8
, 172.16.0.0/12
, 192.168.0.0/16
, useful when the microservices are in
a private network that use the same private block.
public
- use the first publicly accessible IPv4 address offered by all the network interfaces.
{CIDR Block}
- if you specify a CIDR block, we will use the first IP address that matches the given block. This is useful when your machine has more than one IP address and you want to use one from a specific block.
{static IP address}
- if you specify a static IP address, we will use it.
{static domain}
- if you specify a domain name, we will use it.
'use strict';
thorin.plugin('discovery').getRegistry(); // returns
/*
{ api:
[ { name: 'api-xxxxx',
host: '1.2.3.4',
proto: 'http',
port: 14000,
path: '/dispatch',
tags: [],
ttl: 60,
sid: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
healthy: true }
],
mailer:
[ { name: 'mailer-xxxxx',
host: '2.3.4.5',
proto: 'http',
port: 14001,
path: '/dispatch',
tags: [],
ttl: 60,
sid: 'yyyyyyyyyyyyyyyyyyyyy',
healthy: true } ]
}
*/
serviceName
nodes and the plugin will elect()
one. This functionality will essentially enable cross-service
communication.retries
is useful. If setting retry to a higher number, in the event of a microservice being
offline, the plugin will elect a new one and try to dispatch the action to it.
thorin.util.fetch
.
The discovery plugin will extend the thorin.Action
class, adding additonal functionality such as request proxying.
The request proxying is actually adding a custom middleware in the use
chain, that will use the intent input()
as the
payload
to dispatch an action to another microservice.
result
.
rawInput
object and not the filtered input.
serviceName
must have the prefix discovery#
so that the plugin knows that we
are going to proxy to a discovery service, and not an internal proxy. The pattern is discovery#{serviceName}
'use strict'; // The sender app thorin.dispatcher .addAction('myAction') .use((intentObj, next) => { intentObj.input('someValue', 1); // override the intent`s input. next(); }) .before('proxy', (intentObj, serviceData) => { console.log('Will proxy action to ${serviceData.ip}'); }) .proxy('discovery#myOtherService', { action: 'some.other.custom.action' // override the default "myAction" }) .after('proxy', (intentObj, response) => { console.log(`Proxy successful. Response:`, response); }) .use((intentObj) => { console.log("myOtherService responded with: ", intentObj.result()); // here is where we can mutate the result of the intent to send back to the client. intentObj.result("wasCalled", true); intentObj.send(); });
'use strict'; // The receiver app thorin.dispatcher .addAction('some.other.custom.action') .authorize('discovery#proxy') // verifies the incoming authorization token to be from an application within the cluster .use((intentObj, next) => { log.info(`Got called the microservice: ${intentObj.data('proxy_name')} with: ${intentObj.rawResult}`); intentObj.result({ serviceResult: 'right here' }).send(); });
Whenever a service will call another service, it will also generate an Authorization
HTTP header that uses
a signed token to identify the service that is initiating the request. The signature is then verified by the service that is expecting
the action by using the discovery#proxy
authorization middleware.
The discovery#proxy
authorization middleware will place under the intent`s data object the proxy_name
, which is
the name of the service that is calling, and the proxy_type
key, which specifies the service`s type.
A short example can be found in the todo-mailer and todo-app example applications.
You can always create a new issue on GitHub or contact one of the core founders by chat.