vulcain-corejs
Version:
Vulcain micro-service framework
310 lines (308 loc) • 14.1 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments)).next());
});
};
const annotations_1 = require("./../../di/annotations");
const os = require("os");
require("reflect-metadata");
const system_1 = require("./../../configurations/globals/system");
const applicationRequestError_1 = require("./../../errors/applicationRequestError");
const metrics_1 = require("../../metrics/metrics");
const abstractAdapter_1 = require("../../servers/abstractAdapter");
const httpCommandError_1 = require("../../errors/httpCommandError");
const rest = require('unirest');
/**
*
*
* @export
* @abstract
* @class AbstractCommand
* @template T
*/
let AbstractServiceCommand = class AbstractServiceCommand {
/**
* Creates an instance of AbstractCommand.
*
* @param {IContainer} container
* @param {any} providerFactory
*/
constructor(container) {
this.metrics = container.get(annotations_1.DefaultServiceNames.Metrics);
this.initializeMetricsInfo();
this.logger = this.container.get(annotations_1.DefaultServiceNames.Logger);
}
get container() {
return this.requestContext.container;
}
/**
* Set (or reset) user context to use for calling service.
*
* @protected
* @param {string} apiKey - null for reset
* @param {string} tenant - null for reset
*
* @memberOf AbstractServiceCommand
*/
setRequestContext(apiKey, tenant) {
if (!apiKey) {
this.overrideAuthorization = null;
}
else {
this.overrideAuthorization = "ApiKey " + apiKey;
}
this.overrideTenant = tenant;
}
initializeMetricsInfo() {
let dep = this.constructor["$dependency:service"];
if (!dep) {
throw new Error("ServiceDependency annotation is required on command " + Object.getPrototypeOf(this).name);
}
this.setMetricsTags(dep.service, dep.version);
}
setMetricsTags(targetServiceName, targetServiceVersion) {
let exists = system_1.System.manifest.dependencies.services.find(svc => svc.service === targetServiceName && svc.version === targetServiceVersion);
if (!exists) {
system_1.System.manifest.dependencies.services.push({ service: targetServiceName, version: targetServiceVersion });
}
this.customTags = this.metrics.encodeTags("targetServiceName=" + targetServiceName, "targetServiceVersion=" + targetServiceVersion);
this.logger.logAction(this.requestContext, "BC", "Service", targetServiceName + "-" + targetServiceVersion);
}
onCommandCompleted(duration, success) {
this.metrics.timing(AbstractServiceCommand.METRICS_NAME + metrics_1.MetricsConstant.duration, duration, this.customTags);
if (!success)
this.metrics.increment(AbstractServiceCommand.METRICS_NAME + metrics_1.MetricsConstant.failure, this.customTags);
this.logger.logAction(this.requestContext, 'EC');
}
/**
*
*
* @private
* @param {string} serviceName
* @param {number} version
* @returns
*/
createServiceName(serviceName, version) {
if (!serviceName)
throw new Error("You must provide a service name");
if (!version || !version.match(/[0-9]+\.[0-9]+/))
throw new Error("Invalid version number. Must be on the form major.minor");
let alias = system_1.System.resolveAlias(serviceName, version);
if (alias)
return alias;
return system_1.System.createContainerEndpoint(serviceName, version);
}
/**
* get a domain element
* @param serviceName - full service name
* @param version - version of the service
* @param id - Element id
* @param schema - optional element schema
* @returns A vulcain request response
*
* @protected
* @template T
* @param {string} serviceName
* @param {number} version
* @param {string} id
* @param {string} [schema]
* @returns {Promise<QueryResponse<T>>}
*/
getRequestAsync(serviceName, version, id, schema) {
if (system_1.System.hasMocks) {
let result = system_1.System.mocks.applyMockService(serviceName, version, schema ? schema + ".get" : "get", { id });
if (result !== undefined) {
return result;
}
}
let url = schema ? `http://${this.createServiceName(serviceName, version)}/api/{schema}/get/${id}`
: `http://${this.createServiceName(serviceName, version)}/api/get/${id}`;
let res = this.sendRequestAsync("get", url);
return res;
}
/**
*
*
* @protected
* @template T
* @param {string} serviceName
* @param {number} version
* @param {string} action
* @param {*} [query]
* @param {number} [page]
* @param {number} [maxByPage]
* @param {string} [schema]
* @returns {Promise<QueryResponse<T>>}
*/
getQueryAsync(serviceName, version, verb, query, page, maxByPage, schema) {
let args = {};
args.$maxByPage = maxByPage;
args.$page = page;
args.$query = query && JSON.stringify(query);
if (system_1.System.hasMocks) {
let result = system_1.System.mocks.applyMockService(serviceName, version, verb, args);
if (result !== undefined) {
return result;
}
}
let url = system_1.System.createUrl(`http://${this.createServiceName(serviceName, version)}/api/${verb}`, args);
let res = this.sendRequestAsync("get", url);
return res;
}
/**
*
*
* @protected
* @param {string} serviceName
* @param {number} version
* @param {string} action
* @param {*} data
* @returns {Promise<ActionResponse<T>>}
*/
sendActionAsync(serviceName, version, verb, data) {
let command = { params: data, correlationId: this.requestContext.correlationId };
if (system_1.System.hasMocks) {
let result = system_1.System.mocks.applyMockService(serviceName, version, verb, command);
if (result !== undefined) {
return result;
}
}
let url = `http://${this.createServiceName(serviceName, version)}/api/${verb}`;
let res = this.sendRequestAsync("post", url, (req) => req.json(command));
return res;
}
calculateRequestPath() {
if (this.requestContext.correlationId[this.requestContext.correlationId.length - 1] === "-")
this.requestContext.correlationPath += "1";
else {
let parts = this.requestContext.correlationPath.split('-');
let ix = parts.length - 1;
let nb = (parseInt(parts[ix]) || 0) + 1;
parts[ix] = nb.toString();
this.requestContext.correlationPath = parts.join('-');
}
return this.requestContext.correlationPath;
}
setUserContextAsync(request) {
return __awaiter(this, void 0, void 0, function* () {
request.header(abstractAdapter_1.VulcainHeaderNames.X_VULCAIN_TENANT, this.overrideTenant || this.requestContext.tenant);
if (this.overrideAuthorization) {
request.header("Authorization", this.overrideAuthorization);
return;
}
let token = this.requestContext.bearer;
if (!token) {
if (!this.requestContext.user) {
return;
}
let tokens = this.requestContext.container.get("TokenService");
// Ensures jwtToken exists for user context propagation
let result = this.requestContext.bearer = yield tokens.createTokenAsync(this.requestContext.user);
token = result.token;
}
request.header("Authorization", "Bearer " + token);
});
}
/**
* Send a http request
*
* @protected
* @param {string} http verb to use
* @param {string} url
* @param {(req:types.IHttpRequest) => void} [prepareRequest] Callback to configure request before sending
* @returns request response
*/
sendRequestAsync(verb, url, prepareRequest) {
return __awaiter(this, void 0, void 0, function* () {
let request = rest[verb](url);
// Propagate context
request.header(abstractAdapter_1.VulcainHeaderNames.X_VULCAIN_CORRELATION_ID, this.requestContext.correlationId);
request.header(abstractAdapter_1.VulcainHeaderNames.X_VULCAIN_CORRELATION_PATH, this.calculateRequestPath() + "-");
request.header(abstractAdapter_1.VulcainHeaderNames.X_VULCAIN_SERVICE_NAME, system_1.System.serviceName);
request.header(abstractAdapter_1.VulcainHeaderNames.X_VULCAIN_SERVICE_VERSION, system_1.System.serviceVersion);
request.header(abstractAdapter_1.VulcainHeaderNames.X_VULCAIN_ENV, system_1.System.environment);
request.header(abstractAdapter_1.VulcainHeaderNames.X_VULCAIN_CONTAINER, os.hostname());
yield this.setUserContextAsync(request);
prepareRequest && prepareRequest(request);
this.requestContext.logInfo("Calling vulcain service on " + url);
return new Promise((resolve, reject) => {
try {
request.end((response) => {
if (response.error || response.status !== 200) {
let err = new Error(response.error ? response.error.message : response.body);
system_1.System.log.error(this.requestContext, err, `Service request ${verb} ${url} failed with status code ${response.status}`);
reject(err);
return;
}
let vulcainResponse = response.body;
if (vulcainResponse.error) {
system_1.System.log.info(this.requestContext, `Service request ${verb} ${url} failed with status code ${response.status}`);
reject(new applicationRequestError_1.ApplicationRequestError(vulcainResponse.error.message, vulcainResponse.error.errors, response.status));
}
else {
system_1.System.log.info(this.requestContext, `Service request ${verb} ${url} completed with status code ${response.status}`);
resolve(vulcainResponse);
}
});
}
catch (err) {
let msg = `Service request ${verb} ${url} failed`;
system_1.System.log.error(this.requestContext, err, msg);
if (!(err instanceof Error)) {
let tmp = err;
err = new Error(msg);
err.error = tmp;
}
reject(new httpCommandError_1.HttpCommandError(msg, err));
}
});
});
}
exec(kind, serviceName, version, verb, userContext, data, page, maxByPage) {
return __awaiter(this, void 0, void 0, function* () {
switch (kind) {
case 'action': {
userContext && this.setRequestContext(userContext.apiKey, userContext.tenant);
let response = yield this.sendActionAsync(serviceName, version, verb, data);
return response.value;
}
case 'query': {
userContext && this.setRequestContext(userContext.apiKey, userContext.tenant);
let response = yield this.getQueryAsync(serviceName, version, verb, data, page, maxByPage);
return { values: response.value, total: response.total, page };
}
case 'get': {
userContext && this.setRequestContext(userContext.apiKey, userContext.tenant);
let response = yield this.getRequestAsync(serviceName, version, data);
return response.value;
}
}
});
}
runAsync(...args) {
return this.exec(...args);
}
};
AbstractServiceCommand.METRICS_NAME = "service_call";
AbstractServiceCommand = __decorate([
__param(0, annotations_1.Inject(annotations_1.DefaultServiceNames.Container)),
__metadata("design:paramtypes", [Object])
], AbstractServiceCommand);
exports.AbstractServiceCommand = AbstractServiceCommand;
//# sourceMappingURL=abstractServiceCommand.js.map