@carlosbajo/micro
Version:
framework para microservicios con google/pubsub
415 lines (383 loc) • 11 kB
JavaScript
;
const Boom = require('boom');
const path = require('path');
const includeAll = require('include-all');
const _ = require('lodash');
const mongoose = require('mongoose');
const Router = require('koa-router');
const log = require('./logger').logger;
const Reporter = require('./logger').reporter;
const { name } = require(`${process.cwd()}/package.json`);
const logger = log();
const loadClass = require('mongoose-class-wrapper');
const upMachineDate = new Date();
const NotImplemented = Boom.notImplemented;
const MethodNotAllowed = Boom.methodNotAllowed;
const init = {
controllers: {},
routes: {},
modules: {},
services: {},
models: {},
};
const schemaOptions = {
toObject: {
virtuals: true,
},
toJSON: {
virtuals: true,
},
timestamps: true,
};
/**
* controllers - Generate the controllers
*/
const controllers = () =>
new Promise((resolve, reject) => {
includeAll.optional(
{
dirname: path.resolve(`${process.cwd()}/api/controllers`),
filter: /(.+)\.js$/,
identity: false,
useGlobalIdForKeyName: false,
},
(e, res) => {
if (e) {
Reporter.report(e);
return reject(e);
}
return resolve(res);
}
);
});
/**
* modelsSchemas - Generates the model schemas for the api.
*/
const modelsSchemas = () =>
new Promise((resolve, reject) => {
includeAll.optional(
{
dirname: path.resolve(`${process.cwd()}/api/schemas`),
filter: /(.+)\.js/,
identity: false,
useGlobalIdForKeyName: false,
},
(e, res) => {
if (e) {
Reporter.report(e);
return reject(e);
}
return resolve(res);
}
);
});
/**
* modelsClass - Generate the models.
*/
const modelsClass = () =>
new Promise((resolve, reject) => {
includeAll.optional(
{
dirname: path.resolve(`${process.cwd()}/api/models`),
filter: /(.+)\.js$/,
identity: false,
useGlobalIdForKeyName: false,
},
(e, res) => {
if (e) {
Reporter.report(e);
return reject(e);
}
return resolve(res);
}
);
});
/**
*
* @param {Object} ctx
* @param {Object} plcs
*/
const validatePolices = async(ctx, plcs) => {
for (let index = 0; index < plcs.length; index += 1) {
let fnName = plcs[index];
fnName = fnName.replace('fw.', '');
// if (config.polices[fnName]) {
// eslint-disable-next-line
// const response = await config.polices[fnName](ctx); // eslint-disable-line
// if (!response) return response;
// }
}
return true;
};
/**
*
* @param {Models} mdls
* @param {Classes} classes
* @param {DataSources} datasources
*/
const generateModels = (mdls, classes, datasources) => {
const response = {};
Object.keys(mdls).forEach(nam => {
// eslint-disable-line
const model = mdls[nam];
const modelName = _.camelCase(nam);
const modelSchema = new mongoose.Schema(model.properties, schemaOptions);
if (classes[modelName]) {
modelSchema.plugin(loadClass, classes[modelName]);
}
response[modelName] = mongoose.model(modelName, modelSchema);
if (model.softDelete) {
const modelDepName = `${modelName}DLT`;
const modelDepSchema = model.properties;
Object.keys(modelDepSchema).forEach(obj => {
if (modelDepSchema[obj].unique) delete modelDepSchema[obj].unique;
});
response[modelDepName] = datasources(model.datasource).model(
modelDepName,
modelDepSchema
);
}
});
return response;
};
/**
* getRootRoutes - Get the base routes of the API
*/
const getRootRoutes = () => {
const router = new Router();
router.get('/', async ctx => {
ctx.status = 404;
return ctx.status;
});
router.get('/api', async ctx => {
ctx.body = { res: 'API running :D' };
ctx.status = 200;
return ctx.status;
});
return router;
};
/**
*
* @param {Array} controllers - Controladores para generar el formato de ruta.
*/
const makeRoutes = controllers => {
const routes = [];
_.forOwn(controllers, function(value, controller) {
if (controller === 'index') {
_.forOwn(value, (iv, ik) => {
const action = ik.replace('get', '');
const route = `GET /${action}`;
routes.push({ controller, route, action });
});
} else {
_.forOwn(value, (iv, ik) => {
const action = ik.replace('get', '');
const route = `GET /${controller}/${action}`;
routes.push({ controller, route, action });
});
}
});
return routes;
};
/**
*
* @param {*} routes
* @param {*} ctrls
*/
const generateRoutes = (routes, ctrls) => {
const router = new Router();
routes.forEach(r => {
const [verb, uri] = r.route.split(' ');
const route = r.controller;
if (route !== 'identity' && route !== 'globalId') {
if (!ctrls[r.controller]) {
logger.error('Controlador de ruta no encontrado');
return;
}
const action = ctrls[r.controller][r.action];
router[_.toLower(verb)](uri, async ctx => {
try {
if (r.polices) await validatePolices(ctx, r.polices);
return await action(ctx);
} catch (ex) {
ctx.body = new Boom(ex);
ctx.status = ctx.body.output.statusCode;
if (ctx.body.output.statusCode == 500) logger.error(`Error ${ex}`); // eslint-disable-line
return ctx;
}
});
}
});
return router;
};
const communication = () => {
return new Promise((resolve, reject) => {
includeAll.optional(
{
dirname: path.resolve(`${process.cwd()}/api/config`),
filter: /(.+)\.js$/,
identity: false,
useGlobalIdForKeyName: false,
},
(e, res) => {
if (e) {
return reject(e);
}
return resolve(res);
}
);
});
};
/**
* Carga todos los consumidores de la API
*/
const consumers = () =>
new Promise((resolve, reject) => {
includeAll.optional(
{
dirname: path.resolve(`${process.cwd()}/api/consumer`),
filter: /(.+)\.js$/,
identity: false,
useGlobalIdForKeyName: false,
},
(e, res) => {
if (e) {
Reporter.report(e);
return reject(e);
}
return resolve(_.omit(res, ['helper']));
}
);
});
const makeConsumers = async consumers => {
const cons = [];
_.forOwn(consumers, (value, key) => {
_.forOwn(value, (iv, action) => {
let req = `${process.env.NODE_ENV}.req.${name}.${key}.${action}`;
let res = `${process.env.NODE_ENV}.res.${name}.${key}.${action}`;
let micro = `${process.env.NODE_ENV}.micro.${name}.${key}.${action}`;
cons.push({ req, res, micro, action, key });
});
});
return cons;
};
const loadConsumers = async(consumer, pubsub, gPubsub, app, communication) => {
if (!pubsub) {
logger.warn('No pubsub found');
return;
}
// const log = gLogger(logger);
try {
const microTopicsT = await gPubsub.getMicroTopics(communication);
const reqTopics = pubsub.map(topic => ({ topic: topic.req }));
const microTopics = microTopicsT.map(topic => ({ topic: topic.name }));
const microConsumer = gPubsub.kafka.consumer({
groupId: `${name}consumerMicro`,
});
await microConsumer.connect();
const cleanMicro = microTopics.filter(topic =>
topic.topic.includes(`${process.env.NODE_ENV}.`)
);
const cleanReq = reqTopics.filter(topic =>
topic.topic.includes(`${process.env.NODE_ENV}.`)
);
cleanMicro.forEach(async topic => {
await microConsumer.subscribe(topic);
});
cleanReq.forEach(async topic => {
await gPubsub.consumer.subscribe(topic);
});
await microConsumer.run({
eachMessage: ({ topic, partition, message }) => {
if (message.timestamp < upMachineDate) return;
const val = JSON.parse(Buffer.from(message.value).toString());
const promise = gPubsub.reqResources[val.uuid];
if (promise) {
if (!val.data) promise.resolve(val.data);
else if (val.data.isBoom) {
promise.reject(val.data.message);
} else {
try {
promise.resolve(val.data);
} catch (e) {
promise.reject(e.toString());
}
}
delete gPubsub.reqResources[val.uuid];
} else {
logger.error('No subscription promise');
delete gPubsub.reqResources[val.uuid];
}
},
});
await gPubsub.consumer.run({
eachMessage: async({ topic, partition, message }) => {
if (message.timestamp < upMachineDate) return;
const val = JSON.parse(Buffer.from(message.value).toString());
const responseKind = Buffer.from(
message.headers.responseKind
).toString();
const splited = topic.split('.');
const consume = splited[3];
const method = splited[4];
const res = `${responseKind}.${name}.${consume}.${method}`;
try {
const data = await consumer[consume][method](val); // eslint-disable-line
gPubsub.writeMessageToTopic(
JSON.stringify({ data, uuid: val['uuid'] }),
res,
responseKind === 'micro' ? 'resmicro' : 'res'
);
} catch (e) {
Reporter.report(e);
gPubsub.writeMessageToTopic(
JSON.stringify({
data: { isBoom: true, message: e.message },
uuid: val['uuid'],
}),
res
);
}
},
});
} catch (e) {
Reporter.report(e);
throw Boom.conflict('Error', e);
}
};
module.exports = async app => {
try {
app = _.merge(app, init);
app.controllers = await controllers();
app.modelsClass = await modelsClass();
const buildedRoutes = await makeRoutes(app.controllers);
app.routes = buildedRoutes;
app.modelsSchemas = await modelsSchemas();
app.consumers = await consumers();
const buildedConsumers = await makeConsumers(app.consumers);
const { communication: comun } = await communication();
await app.PubSub.setUpProject(buildedConsumers).then(() => {
loadConsumers(app.consumers, buildedConsumers, app.PubSub, app, comun);
});
app.models = generateModels(
app.modelsSchemas,
app.modelsClass,
app.datasources
);
const rootRoutes = getRootRoutes();
const router = generateRoutes(app.routes, app.controllers);
app.use(rootRoutes.routes());
app.use(router.routes());
app.use(
router.allowedMethods({
throw: true,
notImplemented: () => new NotImplemented(),
methodNotAllowed: () => new MethodNotAllowed(),
})
);
app.types = mongoose.Types;
} catch (e) {
Reporter.report(e);
logger.warn(e);
}
};