@vtexlab/planner-message-bus
Version:
A Message Bus that uses AWS SNS, AWS SQS, and AWS EventBridge
157 lines (156 loc) • 8.69 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { Consumer } from 'sqs-consumer';
import { QUEUE_URL_TEMPLATE } from '../utils/constants';
import { DeleteMessageCommand, SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';
import { SpanKind, SpanStatusCode } from '@opentelemetry/api';
import { setDefaultAttributes, startSpan } from '../utils/opentelemetry/observability';
import { EBScheduler } from './eventBridge.service';
const client = new SQSClient();
export function redeliveryMessageQueue(queueName, delaySeconds, message, sendParams, stoppedCondition, callback, errorCallback) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f;
const span = startSpan(redeliveryMessageQueue.name, SpanKind.PRODUCER, {
content: message.Content,
endpoint: queueName
});
const startsAt = (new Date((_c = (_b = (_a = message === null || message === void 0 ? void 0 : message.MessageAttributes) === null || _a === void 0 ? void 0 : _a['RedeliveryStartsAt']) === null || _b === void 0 ? void 0 : _b.StringValue) !== null && _c !== void 0 ? _c : new Date()));
const currentAt = new Date();
const attempt = (parseInt((_f = (_e = (_d = message === null || message === void 0 ? void 0 : message.MessageAttributes) === null || _d === void 0 ? void 0 : _d['RedeliveryAttempt']) === null || _e === void 0 ? void 0 : _e.StringValue) !== null && _f !== void 0 ? _f : '0') + 1);
if (stoppedCondition && stoppedCondition(startsAt, currentAt, attempt)) {
console.warn('stopped queue1 at ' + attempt);
return;
}
try {
const params = Object.assign({ QueueUrl: QUEUE_URL_TEMPLATE(queueName), MessageBody: message.Body, DelaySeconds: delaySeconds, MessageAttributes: {
RedeliveryStartsAt: {
DataType: 'String',
StringValue: startsAt.toString()
},
RedeliveryCurrentAt: {
DataType: 'String',
StringValue: currentAt.toString()
},
RedeliveryAttempt: {
DataType: 'Number',
StringValue: attempt.toString()
}
} }, sendParams);
span === null || span === void 0 ? void 0 : span.setAttributes({
delaySeconds: params.DelaySeconds,
startsAt: startsAt.toString(),
currentAt: currentAt.toString(),
attempt
});
yield ackMessage(queueName, message);
const output = yield client.send(new SendMessageCommand(Object.assign({}, params)));
span === null || span === void 0 ? void 0 : span.addEvent('messageReledivered', { messageId: output.MessageId });
callback === null || callback === void 0 ? void 0 : callback(output, span);
return output.MessageId;
}
catch (error) {
span === null || span === void 0 ? void 0 : span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
span === null || span === void 0 ? void 0 : span.recordException(error);
errorCallback === null || errorCallback === void 0 ? void 0 : errorCallback(error, span);
throw error;
}
finally {
span === null || span === void 0 ? void 0 : span.end();
}
});
}
export function sendMessageQueue(queueName, contentMessage, sendParams, callback, errorCallback) {
return __awaiter(this, void 0, void 0, function* () {
const span = startSpan(sendMessageQueue.name, SpanKind.PRODUCER, {
content: contentMessage,
endpoint: queueName
});
try {
const params = Object.assign({ MessageBody: JSON.stringify(contentMessage), QueueUrl: QUEUE_URL_TEMPLATE(queueName) }, sendParams);
const output = yield client.send(new SendMessageCommand(params));
span === null || span === void 0 ? void 0 : span.addEvent('messageSent', { messageId: output.MessageId });
callback === null || callback === void 0 ? void 0 : callback(output, span);
return output.MessageId;
}
catch (error) {
span === null || span === void 0 ? void 0 : span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
span === null || span === void 0 ? void 0 : span.recordException(error);
errorCallback === null || errorCallback === void 0 ? void 0 : errorCallback(error, span);
throw error;
}
finally {
span === null || span === void 0 ? void 0 : span.end();
}
});
}
export function createConsumerMessages(params) {
const consumer = Consumer.create({
messageAttributeNames: ['All'],
queueUrl: QUEUE_URL_TEMPLATE(params.endpoint),
handleMessage: (message) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const span = startSpan('handleConsumerMessage', SpanKind.CONSUMER);
if (message.Body) {
let messageContent = JSON.parse(message.Body);
if (messageContent.Type === 'Notification') {
messageContent = JSON.parse(messageContent.Message);
}
message.Content = messageContent;
}
span === null || span === void 0 ? void 0 : span.setAttributes(Object.assign({}, setDefaultAttributes(params.endpoint, message.Content)));
try {
yield params.handle(message, span);
span === null || span === void 0 ? void 0 : span.addEvent('messageConsumed', { messageId: message.MessageId });
}
catch (error) {
span === null || span === void 0 ? void 0 : span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
span === null || span === void 0 ? void 0 : span.recordException(error);
yield executeSecondLevelResilience(params.endpoint, message, params.maxRetryCount, params.delaySeconds);
span === null || span === void 0 ? void 0 : span.addEvent('secondLevelResilienceExecuted');
}
finally {
if ((_a = message.Content) === null || _a === void 0 ? void 0 : _a.RuleId) {
try {
yield new EBScheduler().deleteSchedule((_b = message.Content) === null || _b === void 0 ? void 0 : _b.RuleId);
}
catch (_c) { }
}
span === null || span === void 0 ? void 0 : span.end();
}
}),
sqs: client,
batchSize: params.batchSize
});
consumer.start();
return consumer;
}
function executeSecondLevelResilience(queueName, message, maxRetryCount, delaySeconds) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c;
let retryCount = parseInt((_c = (_b = (_a = message === null || message === void 0 ? void 0 : message.MessageAttributes) === null || _a === void 0 ? void 0 : _a['RetryCount']) === null || _b === void 0 ? void 0 : _b.StringValue) !== null && _c !== void 0 ? _c : '0');
yield sendMessageQueue(retryCount <= maxRetryCount ? queueName : `${queueName}-dlq`, message.Body, {
DelaySeconds: retryCount <= maxRetryCount ? delaySeconds : 0,
MessageAttributes: {
'RetryCount': {
DataType: 'Number',
StringValue: (retryCount <= maxRetryCount ? (retryCount + 1) : maxRetryCount).toString()
}
}
});
});
}
function ackMessage(queueName, message) {
return __awaiter(this, void 0, void 0, function* () {
yield client.send(new DeleteMessageCommand({
ReceiptHandle: message.ReceiptHandle,
QueueUrl: QUEUE_URL_TEMPLATE(queueName)
}));
});
}