UNPKG

@carlosbajo/micro

Version:

framework para microservicios con google/pubsub

415 lines (383 loc) 11 kB
'use strict'; 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); } };