@fanckler/processing-utils
Version:
This library contains all common solutions for system microservices
297 lines (286 loc) • 7.82 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.emitEvent = emitEvent;
exports.onEventListeners = onEventListeners;
exports.sendEventMessage = sendEventMessage;
exports.sendResponse = sendResponse;
var _amqplib = _interopRequireDefault(require("amqplib"));
var _lodash = _interopRequireDefault(require("lodash"));
var _events = _interopRequireDefault(require("events"));
var _uuid = require("uuid");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
let instance;
class RmqClient {
constructor() {
this.queues = {};
}
/**
*
* @returns {Promise<RmqClient>}
*/
async init(url) {
try {
this.connection = await _amqplib.default.connect(url);
this.channel = await this.connection.createChannel();
this.channel.responseEmitter = new _events.default();
this.channel.responseEmitter.setMaxListeners(0);
this.channel.consume('amq.rabbitmq.reply-to', msg => {
this.channel.responseEmitter.emit(msg.properties.correlationId, JSON.parse(msg.content.toString()));
}, {
noAck: true
});
console.log('>>> RmqClient connected!');
return this;
} catch (e) {
console.log('>>> RmqClient ERROR!', e.message);
throw e;
}
}
/**
* @param ex
* @param routingKey
* @param msg
* @returns {Promise<void>}
*/
async publish({
ex,
routingKey
}, msg) {
const queue = `${ex}.${routingKey}`;
await this.channel.assertQueue(queue, {
durable: true
});
this.channel.bindQueue(queue, ex, routingKey);
this.channel.publish(ex, routingKey, Buffer.from(msg), {
messageId: (0, _uuid.v4)()
});
}
/**
*
* @param ex
* @param routingKey
* @param messageId
* @param msg
* @param resolve
*/
send({
ex,
routingKey,
messageId = (0, _uuid.v4)(),
expiration
}, msg, resolve, reject) {
const queue = `${ex}.${routingKey}`;
const correlationId = (0, _uuid.v4)();
let isRead = false;
const rejectTimeout = expiration && setTimeout(() => {
if (!isRead) {
reject(new Error('Microservice does not answer!'));
}
return clearTimeout(rejectTimeout);
}, expiration);
this.channel.responseEmitter.once(correlationId, data => {
resolve(data);
isRead = true;
clearTimeout(rejectTimeout);
});
this.channel.sendToQueue(queue, Buffer.from(msg), {
correlationId,
replyTo: 'amq.rabbitmq.reply-to',
messageId,
expiration
});
}
/**
* @param exchange
* @param bindingKey
* @param handler
* @returns {Promise<function(): Promise<void>>}
*/
async subscribe({
exchange,
bindingKey
}, handler) {
const queue = `${exchange}.${bindingKey}`;
if (!this.connection) {
await this.init();
}
await this.channel.assertExchange(exchange, 'direct', {
durable: true
});
if (this.queues[queue]) {
const existingHandler = _lodash.default.find(this.queues[queue], h => h === handler);
if (existingHandler) {
return () => this.unsubscribe(queue, existingHandler);
}
this.queues[queue].push(handler);
return () => this.unsubscribe(queue, handler);
}
await this.channel.assertQueue(queue, {
durable: true
});
this.channel.bindQueue(queue, exchange, bindingKey);
this.queues[queue] = [handler];
this.channel.consume(queue, async msg => {
const ack = _lodash.default.once(() => this.channel.ack(msg));
this.queues[queue].forEach(h => h(msg, ack));
});
return () => this.unsubscribe(queue, handler);
}
/**
* @param queue
* @param handler
* @returns {Promise<void>}
*/
async unsubscribe(queue, handler) {
_lodash.default.pull(this.queues[queue], handler);
}
}
/**
* Singleton
* @returns {Promise<RmqClient>}
*/
RmqClient.getInstance = async function (url) {
if (!instance) {
const broker = new RmqClient();
instance = broker.init(url);
}
return instance;
};
/**
*
* @param url RMQ URL CONNECTION
* @param events
*
* events = [
* {
* queue: exchange который слушаем;
* event: тип ивента;
* callback: (payload, closeQueue) => void;
* payload = {
* data: сообщение, любой тим данных;
* properties: если сообщение ожидает обратного ответа - присылаем replyTo и correlationId любо undefined;
* },
* closeQueue: коллбек закрытия очереди по завершению логики или ошибки;
* }
* ]
*/
function onEventListeners(url, events) {
RmqClient.getInstance(url).then(broker => {
function onEventListen(queue, event, callback, log) {
let msgId;
broker.subscribe({
exchange: queue,
bindingKey: event
}, (data, closeQueue) => {
console.log('broker subscribe', data, queue, event);
try {
const {
replyTo,
correlationId,
messageId
} = data.properties;
console.log(`>>> Subscription RMQ ${queue}.${event} ${log ? JSON.stringify(JSON.parse(data.content)) : '*** hidden ***'}\n`, JSON.stringify({
replyTo,
correlationId,
messageId
}));
if (msgId === messageId) {
console.log('>>> Message with this id has already been processed');
return closeQueue();
}
msgId = messageId;
callback({
data: JSON.parse(data.content),
properties: replyTo && correlationId && {
replyTo,
correlationId
}
}, closeQueue);
} catch (e) {
console.log('broker subscribe error', e);
closeQueue();
}
});
}
if (!Array.isArray(events)) {
throw new Error('onEventListeners expects an array of rabbit connections');
}
events.forEach(item => {
const {
queue,
event,
callback,
log = true
} = item;
onEventListen(queue, event, callback, log);
});
});
}
/**
*
* @param url
* @param queue
* @param event
* @param payload
* @param log
* @returns {Promise<void>}
*/
async function emitEvent(url, {
queue,
event,
payload
}, log = true) {
const broker = await RmqClient.getInstance(url);
await broker.publish({
ex: queue,
routingKey: event
}, JSON.stringify(payload));
console.log(`>>> Emit RMQ ${queue}.${event} \n`, log ? JSON.stringify(payload) : '*** hidden ***');
}
/**
*
* @param url
* @param queue
* @param event
* @param payload
* @param messageId
* @param expiration
* @param log
* @returns {Promise<void>}
*/
function sendEventMessage(url, {
queue,
event,
payload,
messageId,
expiration
}, log = true) {
return new Promise((resolve, reject) => {
RmqClient.getInstance(url).then(broker => {
broker.send({
ex: queue,
routingKey: event,
messageId,
expiration
}, JSON.stringify(payload), resolve, reject);
console.log(`>>> Send message RMQ ${queue}.${event} \n`, log ? JSON.stringify(payload) : '*** hidden ***');
});
});
}
/**
*
* @param replyTo
* @param correlationId
* @param response
* @param log
* @returns {Promise<void>}
*/
async function sendResponse(replyTo, correlationId, response, log = true) {
const broker = await RmqClient.getInstance();
broker.channel.sendToQueue(replyTo, Buffer.from(JSON.stringify(response)), {
correlationId
});
console.log(`>>> Send response RMQ to ${correlationId} -> ${replyTo} \n`, log ? JSON.stringify(response) : '*** hidden ***');
}