@vtexlab/planner-message-bus
Version:
A Message Bus that uses AWS SNS, AWS SQS, and AWS EventBridge
163 lines (162 loc) • 9.23 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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createConsumerMessages = exports.sendMessageQueue = exports.redeliveryMessageQueue = void 0;
const sqs_consumer_1 = require("sqs-consumer");
const constants_1 = require("../utils/constants");
const client_sqs_1 = require("@aws-sdk/client-sqs");
const api_1 = require("@opentelemetry/api");
const observability_1 = require("../utils/opentelemetry/observability");
const eventBridge_service_1 = require("./eventBridge.service");
const client = new client_sqs_1.SQSClient();
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 = (0, observability_1.startSpan)(redeliveryMessageQueue.name, api_1.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: (0, constants_1.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 client_sqs_1.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: api_1.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();
}
});
}
exports.redeliveryMessageQueue = redeliveryMessageQueue;
function sendMessageQueue(queueName, contentMessage, sendParams, callback, errorCallback) {
return __awaiter(this, void 0, void 0, function* () {
const span = (0, observability_1.startSpan)(sendMessageQueue.name, api_1.SpanKind.PRODUCER, {
content: contentMessage,
endpoint: queueName
});
try {
const params = Object.assign({ MessageBody: JSON.stringify(contentMessage), QueueUrl: (0, constants_1.QUEUE_URL_TEMPLATE)(queueName) }, sendParams);
const output = yield client.send(new client_sqs_1.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: api_1.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();
}
});
}
exports.sendMessageQueue = sendMessageQueue;
function createConsumerMessages(params) {
const consumer = sqs_consumer_1.Consumer.create({
messageAttributeNames: ['All'],
queueUrl: (0, constants_1.QUEUE_URL_TEMPLATE)(params.endpoint),
handleMessage: (message) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const span = (0, observability_1.startSpan)('handleConsumerMessage', api_1.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({}, (0, observability_1.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: api_1.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 eventBridge_service_1.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;
}
exports.createConsumerMessages = createConsumerMessages;
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 client_sqs_1.DeleteMessageCommand({
ReceiptHandle: message.ReceiptHandle,
QueueUrl: (0, constants_1.QUEUE_URL_TEMPLATE)(queueName)
}));
});
}