coffee-core
Version:
Coffee IT API core library
117 lines (95 loc) • 3.84 kB
text/typescript
import { Channel, ChannelModel, connect } from 'amqplib';
import { EventPublisher } from './event-publisher.interface';
import { Logger, LoggerService } from '@nestjs/common';
import { CustomLogger } from '../../logger/custom-logger';
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 logger: LoggerService = new CustomLogger(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);
}
}