vulcain-corejs
Version:
Vulcain micro-service framework
286 lines (284 loc) • 12.7 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 dynamicConfiguration_1 = require("./../../configurations/dynamicConfiguration");
const applicationRequestError_1 = require("./../../errors/applicationRequestError");
const metrics_1 = require("../../metrics/metrics");
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();
}
get container() {
return this.requestContext.container;
}
/**
* Set (or reset) apikey authorization to use for calling service.
*
* @protected
* @param {string} apiKey - null for reset
*
* @memberOf AbstractServiceCommand
*/
useApiKey(apiKey) {
if (!apiKey)
this.overrideAuthorization = null;
else
this.overrideAuthorization = "ApiKey " + apiKey;
}
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.metrics.setTags("targetServiceName=" + targetServiceName, "targetServiceVersion=" + targetServiceVersion);
}
onCommandCompleted(duration, success) {
this.metrics.timing(AbstractServiceCommand.METRICS_NAME + metrics_1.MetricsConstant.duration, duration);
this.metrics.increment(AbstractServiceCommand.METRICS_NAME + metrics_1.MetricsConstant.total);
if (!success)
this.metrics.increment(AbstractServiceCommand.METRICS_NAME + metrics_1.MetricsConstant.failure);
}
/**
*
*
* @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");
if (system_1.System.isTestEnvironnment) {
let alias = system_1.System.resolveAlias(serviceName, version);
if (alias)
return alias;
}
// Check if there is a service $redirect config property in shared properties
// Consul = shared/$redirect/serviceName-version
let name = `$redirect.${serviceName}-${version}`;
let prop = dynamicConfiguration_1.DynamicConfiguration.getProperty(name);
if (prop && prop.value) {
if (!prop.value.serviceName && !prop.value.version)
return prop.value;
serviceName = prop.value.serviceName || serviceName;
version = prop.value.version || version;
}
return (serviceName + version).replace(/[\.-]/g, '').toLowerCase() + ":8080";
}
/**
* 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) {
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);
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 };
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;
}
getAuthorization() {
return __awaiter(this, void 0, void 0, function* () {
if (this.overrideAuthorization)
return this.overrideAuthorization;
let token = this.requestContext.bearer;
if (token) {
return "Bearer " + token;
}
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);
return "Bearer " + result.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("X-VULCAIN-CORRELATION-ID", this.requestContext.correlationId);
request.header("X-VULCAIN-CORRELATION-PATH", this.calculateRequestPath() + "-");
request.header("X-VULCAIN-SERVICE-NAME", system_1.System.serviceName);
request.header("X-VULCAIN-SERVICE-VERSION", system_1.System.serviceVersion);
request.header("X-VULCAIN-ENV", system_1.System.environment);
request.header("X-VULCAIN-CONTAINER", os.hostname());
request.header("X-VULCAIN-TENANT", this.requestContext.tenant);
request.header("Authorization", yield this.getAuthorization());
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) {
system_1.System.log.error(this.requestContext, err, `Service request ${verb} ${url} failed`);
reject(err);
}
});
});
}
exec(kind, serviceName, version, verb, apiKey, data, page, maxByPage) {
return __awaiter(this, void 0, void 0, function* () {
switch (kind) {
case 'action': {
this.useApiKey(apiKey);
let response = yield this.sendActionAsync(serviceName, version, verb, data);
return response.value;
}
case 'query': {
this.useApiKey(apiKey);
let response = yield this.getQueryAsync(serviceName, version, verb, data, page, maxByPage);
return { values: response.value, total: response.total, page };
}
case 'get': {
this.useApiKey(apiKey);
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