UNPKG

pandora

Version:

A powerful and lightweight application manager for Node.js applications powered by TypeScript.

226 lines (196 loc) 7.32 kB
'use strict'; import { ServiceRepresentation, ServiceInstanceReference, DepInstances, Service, ProcessRepresentation } from '../domain'; import assert = require('assert'); import {ServiceCore} from './ServiceCore'; import {WorkerContext} from '../application/WorkerContext'; const debug = require('debug')('pandora:ServiceReconciler'); /** * Class ServiceReconciler */ export class ServiceReconciler { protected context: WorkerContext; protected services: Map<string, ServiceInstanceReference> = new Map; protected state: 'notBoot' | 'booting' | 'booted' | 'stoping' = 'notBoot'; protected processRepresentation: ProcessRepresentation; protected workModeByForce; constructor(processRepresentation: ProcessRepresentation, context, workModeByForce?) { this.workModeByForce = workModeByForce; this.processRepresentation = processRepresentation; this.context = context; } /** * Receive a service representation * @param {ServiceRepresentation} serviceRepresentation */ public receiveServiceRepresentation(serviceRepresentation: ServiceRepresentation) { debug('receiveServiceRepresentation() %j', serviceRepresentation); const id = serviceRepresentation.serviceName; if (this.services.has(id)) { return; } const ref: ServiceInstanceReference = { serviceRepresentation: serviceRepresentation, state: 'noinstance' }; this.services.set(serviceRepresentation.serviceName, ref); } /** * Get ordered service id set, order by service weight * @param {"asc" | "desc"} order * @return {Array} */ public getOrderedServiceIdSet(order: 'asc' | 'desc') { const ret = []; for (const id of this.services.keys()) { ret.push({id, weight: this.getWeight(id)}); } return ret.sort((a, b) => { if (order === 'asc') { return a.weight - b.weight; } else { return b.weight - a.weight; } }); } /** * Get service's weight by service's ID (name) * @param id * @param {string[]} chain * @return {any} */ public getWeight(id, chain?: string[]) { chain = Array.from(chain || []); assert(-1 === chain.indexOf(id), `Service name: ${id} in a cyclic dependency chain: ${chain.join(' -> ')} -> ${id}`); if(chain.length > 1 && id === 'all') { throw new Error(`Reserved service name 'all' not allowed to contains within a dependency chain: ${chain.join(' -> ')} -> ${id}`); } if(id === 'all') { return Infinity; } chain.push(id); assert(this.services.has(id), `Could not found service id: ${id}`); const ref = this.services.get(id); const {serviceRepresentation} = ref; if (!serviceRepresentation.dependencies || !serviceRepresentation.dependencies.length) { return 1; } else { const nextLevelWeights = []; for (const nextId of serviceRepresentation.dependencies) { nextLevelWeights.push(this.getWeight(nextId, chain)); } return Math.max.apply(Math, nextLevelWeights) + 1; } } /** * Instantiate all the services */ public instantiate() { for (const {id} of this.getOrderedServiceIdSet('asc')) { assert(this.services.has(id), `Could not found service id: ${id}`); const ref = this.services.get(id); const {state, serviceRepresentation} = ref; debug('instantiateOne() request %s', id); if (state === 'noinstance') { debug('instantiateOne() instantiate %s', id); const deps: string[] = serviceRepresentation.dependencies; const depInstances: DepInstances = {}; if (deps) { for (let depId of deps) { if(depId === 'all') { continue; } depInstances[depId] = this.services.get(depId).serviceCoreInstance; } } const serviceEntry = (<any> serviceRepresentation.serviceEntry).getLazyClass ? (<any> serviceRepresentation.serviceEntry).getLazyClass() : serviceRepresentation.serviceEntry; serviceRepresentation.config = serviceRepresentation.configResolver ? serviceRepresentation.configResolver(this.context.workerContextAccessor, serviceRepresentation.config) : serviceRepresentation.config; const serviceCoreInstance = new ServiceCore({ context: this.context.workerContextAccessor, representation: serviceRepresentation, depInstances: depInstances }, serviceEntry); ref.serviceCoreInstance = serviceCoreInstance; ref.state = 'instanced'; debug('instantiateOne() instanced %s', id); } } } /** * Start all the services * @return {Promise<void>} */ public async start() { debug('start()'); this.instantiate(); // Maybe start mutilate times, only set state at first time if (this.state !== 'booted') { this.state = 'booting'; } for (const {id} of this.getOrderedServiceIdSet('asc')) { assert(this.services.has(id), `Could not found service id: ${id}`); const ref = this.services.get(id); debug('startOne() instanced request %s', id); assert(ref.state !== 'noinstance', 'instantiate first before start ' + id); if (ref.state === 'instanced') { debug('startOne() start %s', id); ref.state = 'booting'; const serviceCore = ref.serviceCoreInstance; // midwayClassicPluginService 中需要在启动过程中,通过 getService 获得到启动过程中的自己,提早实例产生时机 ref.serviceInstance = serviceCore.instantiate(); await serviceCore.start(); ref.state = 'booted'; debug('startOne() booted %s', id); } } debug('start() booted'); this.state = 'booted'; } public async stop() { debug('stop()'); if (this.state === 'notBoot') { return; } for (const {id} of this.getOrderedServiceIdSet('desc')) { assert(this.services.has(id), `Could not found service id: ${id}`); const ref = this.services.get(id); debug('stopOne() instanced request %s', id); assert(ref.state !== 'noinstance', 'instantiate first before stop ' + id); if (ref.state === 'booted') { debug('stopOne() start %s', id); ref.state = 'stopping'; const serviceCore = ref.serviceCoreInstance; await serviceCore.stop(); ref.serviceInstance = serviceCore.getService(); ref.state = 'instanced'; debug('startOne() stopped %s', id); } } debug('stop() stopped'); this.state = 'notBoot'; } public get<T extends Service>(id): T { assert(this.services.has(id), `Could not found service id: ${id}`); const ref = this.services.get(id); assert(ref.serviceInstance, `Service id: ${id} have not instance yet`); return <T> ref.serviceInstance; } public getServiceClass(serviceName) { const ref = this.services.get(serviceName); if (ref) { const serviceRepresentation = ref.serviceRepresentation; const serviceEntry = (<any> serviceRepresentation.serviceEntry).getLazyClass ? (<any> serviceRepresentation.serviceEntry).getLazyClass() : serviceRepresentation.serviceEntry; return serviceEntry; } return null; } public getState() { return this.state; } }