UNPKG

@stoplight/moleculer

Version:

Fast & powerful microservices framework for Node.JS

563 lines (494 loc) 13.2 kB
/* * 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;