UNPKG

coffee-core

Version:

Coffee IT API core library

140 lines (119 loc) 3.93 kB
import { Channel, ChannelModel, connect } from 'amqplib'; import { EventPublisher } from './event-publisher.interface'; import { Logger } from '@nestjs/common'; import { BrokerConfigInterface } from '../subscriber/broker-config.interface'; // Inspired by: https://docs.nestjs.com/fundamentals/custom-providers and https://www.rabbitmq.com/tutorials/tutorial-three-javascript.html export class RabbitMQPublisher implements EventPublisher { private readonly logger = new Logger(RabbitMQPublisher.name); private channel: Channel; private server: ChannelModel; constructor(private readonly config: BrokerConfigInterface) {} public async publishRetry<T>( message: T, routingKey: string, maxRetries = 10, logEvent = true, ): Promise<void> { await this.publishAndRetry(message, routingKey, 0, maxRetries, logEvent); } private async publishAndRetry<T>( message: T, routingKey: string, retries: number, maxRetries: number, logEvent: boolean, ): Promise<void> { try { await this.publish(message, routingKey, logEvent); } catch (e) { const timeOut = setTimeout( async () => { const newRetries = retries + 1; this.logger.error( `Got error when publishing to ${routingKey}. Retrying for the ${newRetries}th time. ${e}`, ); try { await this.publish(message, routingKey, logEvent); } catch (err) { await this.publishRetry(message, routingKey, newRetries, logEvent); } }, this.config.PUBLISH_RETRY_TIMEOUT_MS * 2 ** retries, ); if (retries >= maxRetries) { clearTimeout(timeOut); Logger.error( `Stopped retrying to publish to ${routingKey} after ${retries} times.`, ); } } } public async publish<T>( message: T, routingKey: string, logEvent = true, ): Promise<void> { const channel = await this.getChannel(); const createdExchange = await channel.assertExchange( this.config.EXCHANGE_NAME, this.config.EXCHANGE_TYPE, { durable: this.config.DURABLE_EXCHANGE }, ); const byteMessage = Buffer.from(JSON.stringify(message)); const options = { persistent: this.config.PERSISTENT_MESSAGES }; channel.publish(createdExchange.exchange, routingKey, byteMessage, options); if (logEvent) { this.logger.debug( `Published event to ${routingKey} with payload ${JSON.stringify(message, null, 2)}`, ); } } public async close(): Promise<void> { if (this.channel) { await this.channel.close(); } } public async hasActiveConnection(): Promise<boolean> { try { const server = await this.getServer(); const channel = await this.getChannel(); return server !== undefined && channel !== undefined; } catch (err) { return false; } } private async getChannel(): Promise<Channel> { if (this.channel) { return this.channel; } const server = await this.getServer(); this.channel = await server.createChannel(); this.channel.on('close', () => { this.logger.debug('Channel closed'); this.channel = undefined; }); this.channel.on('err', () => { this.logger.debug('Channel got an error'); this.channel = undefined; }); return this.channel; } private async getServer(): Promise<ChannelModel> { if (this.server) { return this.server; } this.server = await this.connect(); this.server.on('close', () => { this.server = undefined; this.logger.debug('RabbitMQ server closed'); }); this.server.on('err', () => { this.logger.debug('RabbitMQ server got an error'); this.server = undefined; }); return this.server; } private async connect(): Promise<ChannelModel> { return connect(this.config.AMQP_CONNECTION_URI); } }