deep-kernel
Version:
DEEP Kernel Library
863 lines (668 loc) • 18.3 kB
JavaScript
/**
* Created by mgoria on 5/26/15.
*/
/*eslint no-proto: 0*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Kernel = undefined;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _deepCore = require('deep-core');
var _deepCore2 = _interopRequireDefault(_deepCore);
var _deepDi = require('deep-di');
var _deepDi2 = _interopRequireDefault(_deepDi);
var _Exception = require('./Exception/Exception');
var _Instance = require('./Microservice/Instance');
var _MissingMicroserviceException = require('./Exception/MissingMicroserviceException');
var _Injectable = require('./Microservice/Injectable');
var _ContainerAware = require('./ContainerAware');
var _waitUntil = require('wait-until');
var _waitUntil2 = _interopRequireDefault(_waitUntil);
var _util = require('util');
var _util2 = _interopRequireDefault(_util);
var _Loader = require('./Config/Loader');
var _AsyncConfig = require('./Config/Driver/AsyncConfig');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* Deep application kernel
*/
let Kernel = exports.Kernel = function () {
/**
* @param {Array} deepServices
* @param {String} context
*/
function Kernel(deepServices, context) {
_classCallCheck(this, Kernel);
if (Kernel.ALL_CONTEXTS.indexOf(context) === -1) {
throw new _Exception.Exception(`Undefined context "${context}"`);
}
this._config = {};
this._services = deepServices;
this._context = context;
this._runtimeContext = {};
this._contextProvider = null;
this._env = null;
this._container = new _deepDi2.default();
this._isLoaded = false;
this._asyncConfigCache = null;
}
/**
* @returns {Boolean}
*/
_createClass(Kernel, [{
key: 'microservice',
/**
* @param {String|null} identifier
* @returns {Microservice|*}
*/
value: function microservice() {
let identifier = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
if (!identifier) {
identifier = this._config.microserviceIdentifier;
}
for (let microserviceKey in this.microservices) {
if (!this.microservices.hasOwnProperty(microserviceKey)) {
continue;
}
let microservice = this.microservices[microserviceKey];
if (microservice.identifier === identifier) {
return microservice;
}
}
throw new _MissingMicroserviceException.MissingMicroserviceException(identifier);
}
/**
*
* @param {Function} cb
* @returns {Kernel}
*/
}, {
key: 'loadAsyncConfig',
value: function loadAsyncConfig(cb) {
if (this._asyncConfigCache) {
cb(this._asyncConfigCache);
return this;
}
let cache = this.get('cache').system;
let cacheKey = Kernel.ASYNC_CONFIG_CACHE_KEY;
cache.has(cacheKey, (error, exists) => {
this._logErrorIfExistsAndNotProd(error);
if (exists) {
cache.get(cacheKey, (error, rawConfig) => {
this._logErrorIfExistsAndNotProd(error);
if (rawConfig) {
try {
this._asyncConfigCache = JSON.parse(rawConfig);
cb(this._asyncConfigCache);
return;
} catch (error) {
this._logErrorIfExistsAndNotProd(error);
}
}
this._loadAsyncConfig(cache, cacheKey, cb);
});
return;
}
this._loadAsyncConfig(cache, cacheKey, cb);
});
return this;
}
/**
* @param {Cache|*} cache
* @param {String} cacheKey
* @param {Function} cb
* @private
*/
}, {
key: '_loadAsyncConfig',
value: function _loadAsyncConfig(cache, cacheKey, cb) {
_Loader.Loader.asyncConfigLoader(this).load(config => {
cache.set(cacheKey, JSON.stringify(config), 0, error => {
this._logErrorIfExistsAndNotProd(error);
this._asyncConfigCache = config;
cb(config);
});
}, error => {
this._logErrorIfExistsAndNotProd(error);
cb(null);
});
}
/**
* @todo: get rid of this?
*
* @param {Error|String|*} error
* @private
*/
}, {
key: '_logErrorIfExistsAndNotProd',
value: function _logErrorIfExistsAndNotProd(error) {
if (error && this.env !== Kernel.PROD_ENVIRONMENT) {
console.error(error);
}
}
/**
* @param {Function} callback
* @returns {Kernel}
*
* @todo: put config file name into a constant?
*/
}, {
key: 'bootstrap',
value: function bootstrap(callback) {
let rumEvent = {
service: 'deep-kernel',
resourceType: 'Lambda',
eventName: 'KernelLoad',
time: new Date().getTime()
};
// @todo: remove AWS changes the way the things run
// This is used because of AWS Lambda
// context sharing after a cold start
if (this._isLoaded) {
if (this.isBackend) {
rumEvent.eventName = 'KernelLoadFromCache';
rumEvent.resourceId = this.runtimeContext.invokedFunctionArn;
rumEvent.payload = this.config;
this.get('log').rumLog(rumEvent);
}
callback(this);
return this;
}
_Loader.Loader.kernelLoader(this).load(config => {
this.load(config, kernel => {
if (this.isBackend) {
// Log event 'start' time
rumEvent.resourceId = this.runtimeContext.invokedFunctionArn;
this.get('log').rumLog(rumEvent);
// log event 'stop' time
let event = _util2.default._extend({}, rumEvent);
event.payload = kernel.config;
event.time = new Date().getTime();
this.get('log').rumLog(event);
}
callback(kernel);
});
}, error => {
throw new _Exception.Exception(`Error loading kernel: ${error}`);
});
return this;
}
/**
* @returns {Kernel}
*/
}, {
key: 'kernelCleanup',
value: function kernelCleanup() {
let serviceNames = Object.keys(this.services);
for (let serviceName of serviceNames) {
let serviceInstance = this.get(serviceName.toLowerCase());
serviceInstance.cleanup();
}
return this;
}
/**
* Loads all Kernel dependencies
*
* @param {Object} config
* @param {Function} callback
*
* @returns {Kernel}
*/
}, {
key: 'load',
value: function load(config, callback) {
// @todo: remove AWS changes the way the things run
// This is used because of AWS Lambda
// context sharing after a cold start
if (this._isLoaded) {
callback(this);
return this;
}
let originalCallback = callback;
callback = kernel => {
this._isLoaded = true;
originalCallback(kernel);
};
this._config = config;
this._buildContainer(callback);
return this;
}
/**
* @param {*} args
* @returns {*}
*/
}, {
key: 'get',
value: function get() {
var _container;
return (_container = this._container).get.apply(_container, arguments);
}
/**
* @param {Array} args
* @returns {Boolean}
*/
}, {
key: 'has',
value: function has() {
var _container2;
return (_container2 = this._container).has.apply(_container2, arguments);
}
/**
* @returns {Array}
*/
}, {
key: 'getMsParam',
/**
* @param {String} msIdentifier
* @param {String} paramPath
* @param {*} defaultValue
*
* @returns {*}
*/
value: function getMsParam(msIdentifier, paramPath) {
let defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
if (!this._config.microservices.hasOwnProperty(msIdentifier)) {
throw new Error(`Unknown microservice identifier "${msIdentifier}".`);
}
return this._findParam(this._config.microservices[msIdentifier].parameters, paramPath, defaultValue);
}
/**
* @param {String} paramPath
* @param {*} defaultValue
*
* @returns {*}
*/
}, {
key: 'getParam',
value: function getParam(paramPath) {
let defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
return this._findParam(this._config.globals, paramPath, defaultValue);
}
/**
* @param {*} params
* @param {String} paramPath
* @param {*} defaultValue
*
* @returns {*}
*
* @private
*/
}, {
key: '_findParam',
value: function _findParam(params, paramPath, defaultValue) {
let paramParts = paramPath.split('|').map(x => x.trim());
let result = params;
for (let i = 0; i < paramParts.length; i++) {
let param = paramParts[i];
if (!result || typeof result !== 'object' && !result.hasOwnProperty(param)) {
return defaultValue;
}
result = result[param];
}
return result;
}
/**
* @returns {Microservice[]}
*/
}, {
key: '_buildContainer',
/**
* Loads all parameters and services into DI container
*
* @param {Function} callback
*/
value: function _buildContainer(callback) {
this._env = this._config.env;
this._container.addParameter(Kernel.KERNEL, this);
this._container.addParameter(Kernel.CONTEXT, {
environment: this._env,
isFrontend: this.isFrontend,
isBackend: this.isBackend
});
this._container.addParameter(Kernel.MICROSERVICES, _Instance.Instance.createVector(this._config));
this._container.addParameter(Kernel.CONFIG, this._config);
this._container.localBackend = _deepCore2.default.IS_DEV_SERVER;
let bootingServices = 0;
for (let serviceKey in this._services) {
if (!this._services.hasOwnProperty(serviceKey)) {
continue;
}
let serviceInstance = new this._services[serviceKey]();
bootingServices++;
serviceInstance.kernel = this;
serviceInstance.localBackend = _deepCore2.default.IS_DEV_SERVER;
serviceInstance.boot(this, () => {
bootingServices--;
});
this._container.addService(serviceInstance.name, Kernel._createProxyIfNeeded(serviceInstance));
}
(0, _waitUntil2.default)().interval(10).times(999999 // @todo: get rid of magic here...
).condition(cb => {
process.nextTick(() => {
cb(bootingServices <= 0);
});
}).done(() => {
callback(this);
});
}
/**
* @param {ContainerAware|Object} serviceObj
* @returns {ContainerAware|Proxy|Object}
* @private
*/
}, {
key: 'isLoaded',
get: function get() {
return this._isLoaded;
}
/**
* @returns {Object}
*/
}, {
key: 'runtimeContext',
get: function get() {
return this._runtimeContext;
}
/**
* @param {Object} runtimeContext
*/
,
set: function set(runtimeContext) {
this._runtimeContext = runtimeContext;
}
/**
* @returns {Object}
*/
}, {
key: 'contextProvider',
get: function get() {
return this._contextProvider;
}
/**
* @param {Object} contextProvider
*/
,
set: function set(contextProvider) {
this._contextProvider = contextProvider;
}
/**
* @returns {Microservice|*}
*/
}, {
key: 'rootMicroservice',
get: function get() {
for (let microserviceKey in this.microservices) {
if (!this.microservices.hasOwnProperty(microserviceKey)) {
continue;
}
let microservice = this.microservices[microserviceKey];
if (microservice.isRoot) {
return microservice;
}
}
// this should never happen...
throw new _MissingMicroserviceException.MissingMicroserviceException('ROOT');
}
/**
* @todo: add advanced criteria for account microservice
* @returns {Microservice|null}
*/
}, {
key: 'accountMicroservice',
get: function get() {
for (let microserviceKey in this.microservices) {
if (!this.microservices.hasOwnProperty(microserviceKey)) {
continue;
}
let microservice = this.microservices[microserviceKey];
if (microservice.identifier === Kernel.ACCOUNT_MICROSERVICE_IDENTIFIER) {
return microservice;
}
}
return null;
}
}, {
key: 'services',
get: function get() {
return this._services;
}
/**
* @returns {DI}
*/
}, {
key: 'container',
get: function get() {
return this._container;
}
/**
* @returns {Boolean}
*/
}, {
key: 'isRumEnabled',
get: function get() {
return this.has('log') && this.get('log').isRumEnabled();
}
/**
* @returns {Boolean}
*/
}, {
key: 'isFrontend',
get: function get() {
return this._context === Kernel.FRONTEND_CONTEXT;
}
/**
* @returns {Boolean}
*/
}, {
key: 'isLocalhost',
get: function get() {
return this.isFrontend && ['localhost', '127.0.0.1', '0.0.0.0', '::1'].indexOf(window.location.hostname) !== -1;
}
/**
* @returns {Boolean}
*/
}, {
key: 'isBackend',
get: function get() {
return this._context === Kernel.BACKEND_CONTEXT;
}
/**
* @returns {String}
*/
}, {
key: 'buildId',
get: function get() {
return this._config.deployId || '';
}
/**
* @returns {String}
*/
}, {
key: 'context',
get: function get() {
return this._context;
}
/**
* @returns {String}
*/
}, {
key: 'env',
get: function get() {
return this._env;
}
/**
* @returns {Object}
*/
}, {
key: 'config',
get: function get() {
// @todo - create a class DeepConfig or smth, that will hold global config and expose shortcuts to different options
return this._config;
}
}, {
key: 'microservices',
get: function get() {
return this._container.get(Kernel.MICROSERVICES);
}
}], [{
key: '_createProxyIfNeeded',
value: function _createProxyIfNeeded(serviceObj) {
if (serviceObj === serviceObj.service) {
return serviceObj;
} else if (!serviceObj.hasOwnProperty('apply')) {
return serviceObj.service;
}
let proxy = new Proxy(serviceObj, serviceObj.service);
proxy.__proto__ = this.__proto__;
proxy.constructor.prototype = this.constructor.prototype;
return proxy;
}
/**
* @returns {MicroserviceInjectable}
*/
}, {
key: 'MicroserviceInjectable',
get: function get() {
return _Injectable.Injectable;
}
/**
* @returns {ContainerAware}
*/
}, {
key: 'ContainerAware',
get: function get() {
return _ContainerAware.ContainerAware;
}
/**
* @returns {String}
*/
}, {
key: 'FRONTEND_BOOTSTRAP_VECTOR',
get: function get() {
return 'deep_frontend_bootstrap_vector';
}
/**
* @returns {String}
*/
}, {
key: 'CONFIG',
get: function get() {
return 'deep_config';
}
/**
* @returns {String}
*/
}, {
key: 'KERNEL',
get: function get() {
return 'deep_kernel';
}
/**
* @returns {String}
*/
}, {
key: 'CONTEXT',
get: function get() {
return 'deep_context';
}
/**
* @returns {String}
*/
}, {
key: 'MICROSERVICES',
get: function get() {
return 'deep_microservices';
}
/**
* @returns {String}
*/
}, {
key: 'FRONTEND_CONTEXT',
get: function get() {
return 'frontend-ctx';
}
/**
* @returns {String}
*/
}, {
key: 'BACKEND_CONTEXT',
get: function get() {
return 'backend-ctx';
}
/**
* @returns {Array}
*/
}, {
key: 'ALL_CONTEXTS',
get: function get() {
return [Kernel.FRONTEND_CONTEXT, Kernel.BACKEND_CONTEXT];
}
/**
* @returns {String}
*/
}, {
key: 'PROD_ENVIRONMENT',
get: function get() {
return 'prod';
}
/**
* @returns {String}
*/
}, {
key: 'STAGE_ENVIRONMENT',
get: function get() {
return 'stage';
}
/**
* @returns {String}
*/
}, {
key: 'TEST_ENVIRONMENT',
get: function get() {
return 'test';
}
/**
* @returns {String}
*/
}, {
key: 'DEV_ENVIRONMENT',
get: function get() {
return 'dev';
}
/**
* @returns {String}
*/
}, {
key: 'ASYNC_CONFIG_FILE',
get: function get() {
return _AsyncConfig.AsyncConfig.DEFAULT_CONFIG_FILE;
}
/**
* @returns {String}
*/
}, {
key: 'ASYNC_CONFIG_CACHE_KEY',
get: function get() {
return 'asyncConfig';
}
/**
* @returns {String}
*/
}, {
key: 'ACCOUNT_MICROSERVICE_IDENTIFIER',
get: function get() {
return 'deep-account';
}
/**
* @returns {Array}
*/
}, {
key: 'ALL_ENVIRONMENTS',
get: function get() {
return [Kernel.PROD_ENVIRONMENT, Kernel.STAGE_ENVIRONMENT, Kernel.TEST_ENVIRONMENT, Kernel.DEV_ENVIRONMENT];
}
}]);
return Kernel;
}();