@stoplight/moleculer
Version:
Fast & powerful microservices framework for Node.JS
563 lines (494 loc) • 13.2 kB
JavaScript
/*
* moleculer
* Copyright (c) 2020 MoleculerJS (https://github.com/moleculerjs/moleculer)
* MIT Licensed
*/
"use strict";
const _ = require("lodash");
const utils = require("../utils");
const Strategies = require("../strategies");
const Discoverers = require("./discoverers");
const NodeCatalog = require("./node-catalog");
const ServiceCatalog = require("./service-catalog");
const EventCatalog = require("./event-catalog");
const ActionCatalog = require("./action-catalog");
const ActionEndpoint = require("./endpoint-action");
const { METRIC } = require("../metrics");
/**
* Service Registry
*
* @class Registry
*/
class Registry {
/**
* Creates an instance of Registry.
*
* @param {any} broker
* @memberof Registry
*/
constructor(broker) {
this.broker = broker;
this.metrics = broker.metrics;
this.logger = broker.getLogger("registry");
this.opts = Object.assign({}, broker.options.registry);
this.StrategyFactory = Strategies.resolve(this.opts.strategy);
this.logger.info(`Strategy: ${this.StrategyFactory.name}`);
this.discoverer = Discoverers.resolve(this.opts.discoverer);
this.logger.info(`Discoverer: ${this.broker.getConstructorName(this.discoverer)}`);
this.nodes = new NodeCatalog(this, broker);
this.services = new ServiceCatalog(this, broker);
this.actions = new ActionCatalog(this, broker, this.StrategyFactory);
this.events = new EventCatalog(this, broker, this.StrategyFactory);
this.registerMoleculerMetrics();
this.updateMetrics();
}
init(/*broker*/) {
this.discoverer.init(this);
}
stop() {
return this.discoverer.stop();
}
/**
* Register Moleculer Core metrics.
*/
registerMoleculerMetrics() {
if (!this.broker.isMetricsEnabled()) return;
this.metrics.register({
name: METRIC.MOLECULER_REGISTRY_NODES_TOTAL,
type: METRIC.TYPE_GAUGE,
description: "Number of registered nodes"
});
this.metrics.register({
name: METRIC.MOLECULER_REGISTRY_NODES_ONLINE_TOTAL,
type: METRIC.TYPE_GAUGE,
description: "Number of online nodes"
});
this.metrics.register({
name: METRIC.MOLECULER_REGISTRY_SERVICES_TOTAL,
type: METRIC.TYPE_GAUGE,
description: "Number of registered services"
});
this.metrics.register({
name: METRIC.MOLECULER_REGISTRY_SERVICE_ENDPOINTS_TOTAL,
type: METRIC.TYPE_GAUGE,
labelNames: ["service"],
description: "Number of service endpoints"
});
this.metrics.register({
name: METRIC.MOLECULER_REGISTRY_ACTIONS_TOTAL,
type: METRIC.TYPE_GAUGE,
description: "Number of registered actions"
});
this.metrics.register({
name: METRIC.MOLECULER_REGISTRY_ACTION_ENDPOINTS_TOTAL,
type: METRIC.TYPE_GAUGE,
labelNames: ["action"],
description: "Number of action endpoints"
});
this.metrics.register({
name: METRIC.MOLECULER_REGISTRY_EVENTS_TOTAL,
type: METRIC.TYPE_GAUGE,
description: "Number of registered events"
});
this.metrics.register({
name: METRIC.MOLECULER_REGISTRY_EVENT_ENDPOINTS_TOTAL,
type: METRIC.TYPE_GAUGE,
labelNames: ["event"],
description: "Number of event endpoints"
});
}
/**
* Update metrics.
*/
updateMetrics() {
if (!this.broker.isMetricsEnabled()) return;
this.metrics.set(METRIC.MOLECULER_REGISTRY_NODES_TOTAL, this.nodes.count());
this.metrics.set(METRIC.MOLECULER_REGISTRY_NODES_ONLINE_TOTAL, this.nodes.onlineCount());
const services = this.services.list({
grouping: true,
onlyLocal: false,
onlyAvailable: false,
skipInternal: false,
withActions: false,
withEvents: false
});
this.metrics.set(METRIC.MOLECULER_REGISTRY_SERVICES_TOTAL, services.length);
services.forEach(svc =>
this.metrics.set(
METRIC.MOLECULER_REGISTRY_SERVICE_ENDPOINTS_TOTAL,
svc.nodes ? svc.nodes.length : 0,
{ service: svc.fullName }
)
);
const actions = this.actions.list({ withEndpoints: true });
this.metrics.set(METRIC.MOLECULER_REGISTRY_ACTIONS_TOTAL, actions.length);
actions.forEach(item =>
this.metrics.set(
METRIC.MOLECULER_REGISTRY_ACTION_ENDPOINTS_TOTAL,
item.endpoints ? item.endpoints.length : 0,
{ action: item.name }
)
);
const events = this.events.list({ withEndpoints: true });
this.metrics.set(METRIC.MOLECULER_REGISTRY_EVENTS_TOTAL, events.length);
events.forEach(item =>
this.metrics.set(
METRIC.MOLECULER_REGISTRY_EVENT_ENDPOINTS_TOTAL,
item.endpoints ? item.endpoints.length : 0,
{ event: item.name }
)
);
}
/**
* Register local service
*
* @param {Service} svc
* @memberof Registry
*/
registerLocalService(svc) {
if (!this.services.has(svc.fullName, this.broker.nodeID)) {
const service = this.services.add(this.nodes.localNode, svc, true);
if (svc.actions) this.registerActions(this.nodes.localNode, service, svc.actions);
if (svc.events) this.registerEvents(this.nodes.localNode, service, svc.events);
this.nodes.localNode.services.push(service);
this.regenerateLocalRawInfo(this.broker.started);
this.logger.info(`'${svc.name}' service is registered.`);
this.broker.servicesChanged(true);
this.updateMetrics();
}
}
/**
* Register remote services
*
* @param {Nodeany} node
* @param {Array} serviceList
* @memberof Registry
*/
registerServices(node, serviceList) {
serviceList.forEach(svc => {
if (!svc.fullName)
svc.fullName = this.broker.ServiceFactory.getVersionedFullName(
svc.name,
svc.version
);
let prevActions, prevEvents;
let service = this.services.get(svc.fullName, node.id);
if (!service) {
service = this.services.add(node, svc, false);
} else {
prevActions = Object.assign({}, service.actions);
prevEvents = Object.assign({}, service.events);
service.update(svc);
}
//Register actions
if (svc.actions) {
this.registerActions(node, service, svc.actions);
}
// remove old actions which is not exist
if (prevActions) {
_.forIn(prevActions, (action, name) => {
if (!svc.actions || !svc.actions[name]) {
this.unregisterAction(node, name);
}
});
}
//Register events
if (svc.events) {
this.registerEvents(node, service, svc.events);
}
// remove old events which is not exist
if (prevEvents) {
_.forIn(prevEvents, (event, name) => {
if (!svc.events || !svc.events[name]) {
this.unregisterEvent(node, name);
}
});
}
});
// remove old services which is not exist in new serviceList
// Please note! At first, copy the array because you can't remove items inside forEach
const prevServices = Array.from(this.services.services);
prevServices.forEach(service => {
if (service.node != node) return;
let exist = false;
serviceList.forEach(svc => {
if (service.equals(svc.fullName)) exist = true;
});
// This service is removed on remote node!
if (!exist) {
this.unregisterService(service.fullName, node.id);
}
});
this.broker.servicesChanged(false);
this.updateMetrics();
}
/**
* Check the action visiblity.
*
* Available values:
* - "published" or `null`: public action and can be published via API Gateway
* - "public": public action, can be called remotely but not published via API GW
* - "protected": can be called from local services
* - "private": can be called from internally via `this.actions.xy()` inside Service
*
* @param {*} action
* @param {*} node
* @returns
* @memberof Registry
*/
checkActionVisibility(action, node) {
if (
action.visibility == null ||
action.visibility == "published" ||
action.visibility == "public"
)
return true;
if (action.visibility == "protected" && node.local) return true;
return false;
}
/**
* Register service actions
*
* @param {Node} node
* @param {Service} service
* @param {Object} actions
* @memberof Registry
*/
registerActions(node, service, actions) {
_.forIn(actions, action => {
if (!this.checkActionVisibility(action, node)) return;
if (node.local) {
action.handler = this.broker.middlewares.wrapHandler(
"localAction",
action.handler,
action
);
} else if (this.broker.transit) {
action.handler = this.broker.middlewares.wrapHandler(
"remoteAction",
this.broker.transit.request.bind(this.broker.transit),
{ ...action, service }
);
}
if (this.broker.options.disableBalancer && this.broker.transit)
action.remoteHandler = this.broker.middlewares.wrapHandler(
"remoteAction",
this.broker.transit.request.bind(this.broker.transit),
{ ...action, service }
);
this.actions.add(node, service, action);
service.addAction(action);
});
}
/**
* Create a local Endpoint for private actions
*
* @param {Action} action
* @returns {ActionEndpoint}
* @memberof Registry
*/
createPrivateActionEndpoint(action) {
return new ActionEndpoint(this, this.broker, this.nodes.localNode, action.service, action);
}
/**
* Check the service is exist
*
* @param {String} fullName
* @param {String} nodeID
* @returns {Boolean}
* @memberof Registry
*/
hasService(fullName, nodeID) {
return this.services.has(fullName, nodeID);
}
/**
* Get endpoint list of action by name
*
* @param {String} actionName
* @returns {EndpointList}
* @memberof Registry
*/
getActionEndpoints(actionName) {
return this.actions.get(actionName);
}
/**
* Get an endpoint of action on a specified node
*
* @param {String} actionName
* @param {String} nodeID
* @returns {Endpoint}
* @memberof Registry
*/
getActionEndpointByNodeId(actionName, nodeID) {
const list = this.actions.get(actionName);
if (list) return list.getEndpointByNodeID(nodeID);
}
/**
* Unregister service
*
* @param {String} fullName
* @param {String?} nodeID
* @memberof Registry
*/
unregisterService(fullName, nodeID) {
this.services.remove(fullName, nodeID || this.broker.nodeID);
if (!nodeID || nodeID == this.broker.nodeID) {
this.regenerateLocalRawInfo(true);
}
}
/**
* Unregister all services by nodeID
*
* @param {String} nodeID
* @memberof Registry
*/
unregisterServicesByNode(nodeID) {
this.services.removeAllByNodeID(nodeID);
}
/**
* Unregister an action by node & name
*
* @param {Node} node
* @param {String} actionName
* @memberof Registry
*/
unregisterAction(node, actionName) {
this.actions.remove(actionName, node.id);
}
/**
* Register service events
*
* @param {Node} node
* @param {ServiceItem} service
* @param {Object} events
* @memberof Registry
*/
registerEvents(node, service, events) {
_.forIn(events, event => {
if (node.local)
event.handler = this.broker.middlewares.wrapHandler(
"localEvent",
event.handler,
event
);
this.events.add(node, service, event);
service.addEvent(event);
});
}
/**
* Unregister event by name & node
*
* @param {Node} node
* @param {String} eventName
* @memberof Registry
*/
unregisterEvent(node, eventName) {
this.events.remove(eventName, node.id);
}
/**
* Generate local raw info for INFO packet
*
* @memberof Registry
*/
regenerateLocalRawInfo(incSeq) {
let node = this.nodes.localNode;
if (incSeq) node.seq++;
const rawInfo = _.pick(node, [
"ipList",
"hostname",
"instanceID",
"client",
"config",
"port",
"seq",
"metadata"
]);
if (this.broker.started) rawInfo.services = this.services.getLocalNodeServices();
else rawInfo.services = [];
// Make to be safety
node.rawInfo = utils.safetyObject(rawInfo, this.broker.options);
return node.rawInfo;
}
/**
* Generate local node info for INFO packets
*
* @returns
* @memberof Registry
*/
getLocalNodeInfo(force) {
if (force || !this.nodes.localNode.rawInfo) return this.regenerateLocalRawInfo();
return this.nodes.localNode.rawInfo;
}
/**
* Generate node info for INFO packets
*
* @returns
* @memberof Registry
*/
getNodeInfo(nodeID) {
const node = this.nodes.get(nodeID);
if (!node) return null;
if (node.local) return this.getLocalNodeInfo();
return node.rawInfo;
}
/**
* Process an incoming node INFO packet
*
* @param {any} payload
* @returns
* @memberof Registry
*/
processNodeInfo(payload) {
return this.nodes.processNodeInfo(payload);
}
/**
* Get list of registered nodes
*
* @param {object} opts
* @returns
* @memberof Registry
*/
getNodeList(opts) {
return this.nodes.list(opts);
}
/**
* Get list of registered services
*
* @param {object} opts
* @returns
* @memberof Registry
*/
getServiceList(opts) {
return this.services.list(opts);
}
/**
* Get list of registered actions
*
* @param {object} opts
* @returns
* @memberof Registry
*/
getActionList(opts) {
return this.actions.list(opts);
}
/**
* Get list of registered events
*
* @param {object} opts
* @returns
* @memberof Registry
*/
getEventList(opts) {
return this.events.list(opts);
}
/**
* Get a raw info list from nodes
*
* @returns {Array<Object>}
* @memberof Registry
*/
getNodeRawList() {
return this.nodes.toArray().map(node => node.rawInfo);
}
}
module.exports = Registry;