Реализация шаблона Saga в микросервисах с помощью Node.js



Книга Реализация шаблона Saga в микросервисах с помощью Node.js



Целостность данных в монолитных приложениях во многом определяется свойствами атомарности, непротиворечивости, изолированности и долговечности (Atomicity, Consistency, Isolation, Durability  —  ACID). При этом по мере усложнения приложений недостатки монолитной модели становятся все более очевидными. Эффективно устранить многие из них позволяет микросервисная архитектура. Однако она также создает и серьезные проблемы для управления транзакциями и согласованности данных между независимыми базами данных и сервисами.


Структурированное решение этой проблемы предоставляет шаблон Saga. Он предлагает системный подход к управлению транзакциями между несколькими микросервисами. При этом устраняются проблемы распределенных транзакций в соответствии с принципами архитектуры микросервисов, характеризующейся слабой связностью и возможностью независимого развертывания сервисов.




Что такое шаблон Saga?


Saga  —  это шаблон проектирования, используемый для управления транзакциями и обеспечивающий согласованность данных между отдельными сервисами в распределенной системе, особенно в архитектуре микросервисов. В отличие от традиционных монолитных приложений, где транзакции выполняются в единой базе данных, не нарушая их согласованность, микросервисы часто обращаются к разным базам данных, что усложняет поддержание целостности данных во всей системе с использованием стандартных транзакций ACID. Шаблон Saga решает эту проблему, разбивая транзакцию на более мелкие локальные составляющие, обрабатываемые различными сервисами.


Шаблон включает три основных компонента: локальные транзакции, компенсационные транзакции и коммуникации. Они позволяют понять особенности работы Saga.



  • Локальные транзакции. Каждый шаг бизнес-процесса выполняется как локальная транзакция в соответствующем сервисе.

  • Компенсационные транзакции. Если одна из локальных транзакций завершается неудачно, компенсационные транзакции запускаются в тех службах, где предыдущие шаги были успешно выполнены. Компенсирующие транзакции  —  это по сути операции отмены, гарантирующие возврат системы в согласованное состояние.

  • Коммуникации. Сервисы взаимодействуют между собой посредством сообщений или событий. Это может происходить синхронно, но чаще асинхронно с использованием очередей сообщений или шин событий. В случае сбоя/отказа для обеспечения стабильности системы контроллер выполнения (Saga Execution Controller) запускает эти события.




Как реализовать шаблон Saga с помощью Node.js


Существует два подхода к реализации шаблона «Saga»: «Saga на основе хореографии» и «Saga на основе оркестрации».



  • Saga на основе оркестрации: один оркестратор (аранжировщик) управляет всеми транзакциями и сервисами для выполнения локальных транзакций.

  • Saga на основе хореографии: все сервисы, являющиеся частью распределенной транзакции, публикуют новое событие после завершения своей локальной транзакции.


В этом примере использован подход «Saga на основе хореографии» и реальный сценарий бронирования номеров в отеле с тремя микросервисами: бронирования, оплаты и уведомлений (Booking Service, Payment Service и Notification Service).




  • Booking Service. Запускается процесс резервирования номера. Это первая локальная транзакция. После подтверждения оплаты отправляется сообщение для обработки платежа в Payment Service.

  • Payment Service. Прием сообщения и обработка платежа. Если платеж прошел успешно, совершается локальная транзакция с информированием сервисов бронирования и уведомлений (Booking Service и Notification Service).

  • Notification Service. После получения подтверждения об успешной оплате, сервис отправляет пользователю электронное письмо с подтверждением бронирования.

  • Обработка ошибок. Если Payment Service обнаружит проблемы (например, отказ в проведении платежа), он возвращает в Booking Service сообщение об ошибке. После этого Booking Service выполняет компенсирующую транзакцию для отмены бронирования номера, обеспечивающую возврат системы в исходное согласованное состояние.


Необходимые условия



  • Проект Node.js с установленными зависимостями (express, amqplib, nodemailer, mongoose и dotenv).

  • Сервер RabbitMQ, работающий локально или удаленно.

  • Сервер электронной почты или сервис для Notification Service (например, Nodemailer с SMTP или сервисом API электронной почты).


Шаг 1. Создание конечной точки API для запуска процесса бронирования


// booking-service.js

const express = require('express');
const amqp = require('amqplib');
const app = express();
app.use(express.json());
//Подключение к RabbitMQ
const rabbitUrl = 'amqp://localhost';
let channel;
async function connectRabbitMQ() {
const connection = await amqp.connect(rabbitUrl);
channel = await connection.createChannel();
await channel.assertQueue('payment_queue');
}
// Конечная точка для бронирования номера
app.post('/book', async (req, res) => {
//Сохранение результатов бронирование в базе данных и попытка резервирования номера.

booking = { /* ... */ };
// ... логика бронирования
if (bookingReservedSuccessfully) {
await publishToQueue('payment_queue', booking);
return res.status(200).json({ message: 'Booking initiated', booking });
} else {
return res.status(500).json({ message: 'Booking failed' });
}
});
//Запуск сервера и подключение к RabbitMQ
const PORT = 3000;
app.listen(PORT, async () => {
console.log(Booking Service listening on port ${PORT} );
await connectRabbitMQ();
});

В процессе нового бронирования Booking Service обрабатывает запросы HTTP POST. Сервис пытается зарезервировать номер и в случае успеха отправляет сообщение в Payment Service через очередь сообщений (Payment_queue) RabbitMQ.


Шаг 2. Создание конечной точки API для прослушивания бронирований и обработки платежей


// payment-service.js
const amqp = require('amqplib');
const rabbitUrl = 'amqp://localhost';
let channel;

async function connectRabbitMQ() {
const connection = await amqp.connect(rabbitUrl);
channel = await connection.createChannel();
await channel.assertQueue('notification_queue');
await channel.assertQueue('compensation_queue');
channel.consume('payment_queue', async (msg) => {
const booking = JSON.parse(msg.content.toString());
//Вставка логоки оформления платежа
const paymentSuccess = true; //Замена актуальным условием успешного платежа
if (paymentSuccess) {
await channel.sendToQueue('notification_queue', Buffer.from(JSON.stringify(booking)));
} else {
await channel.sendToQueue('compensation_queue', Buffer.from(JSON.stringify(booking)));
}
channel.ack(msg);
});
}
connectRabbitMQ();

Payment Service прослушивает сообщения в очереди Payment_queue и обрабатывает платеж. Затем в зависимости от результата либо отправляет сообщение в notification_queue (если платеж прошел), либо в compensation_queue (если платеж не выполнен).


Шаг 3. Создание конечной точки API для отслеживания успешных платежей и отправки электронной почты.


// notification-service.js
const amqp = require('amqplib');
const nodemailer = require('nodemailer');

const rabbitUrl = 'amqp://localhost';
let channel;
async function connectRabbitMQ() {
const connection = await amqp.connect(rabbitUrl);
channel = await connection.createChannel();
await channel.assertQueue('notification_queue');
channel.consume('notification_queue', async (msg) => {
const booking = JSON.parse(msg.content.toString());
//Вставка логики для отправки уведомления пользователю по электронной почте.
console.log(Sending booking confirmation for bookingId: ${booking.id} );
//Настройка транспорта nodemailer
//Отправка электронного соообщения ...
channel.ack(msg);
});
}
connectRabbitMQ();

Notification Service прослушивает сообщения из очереди notification_queue. Получив сообщение он отправляет клиенту подтверждение по электронной почте.


Шаг 4. Создание сервиса компенсации для обработки ошибок


// compensation-service.js
const amqp = require('amqplib');

const rabbitUrl = 'amqp://localhost';
let channel;
async function connectRabbitMQ() {
const connection = await amqp.connect(rabbitUrl);
channel = await connection.createChannel();
await channel.assertQueue('compensation_queue');
channel.consume('compensation_queue', async (msg) => {
const booking = JSON.parse(msg.content.toString());
//Вставка логики для отмены бронирования
console.log(Compensating transaction: cancelling bookingId: ${booking.id} );
//Обновляем статус бронирования в базе данных на «CANCELLED» или подобное...
channel.ack(msg);
});
}
connectRabbitMQ();

Сервис компенсации прослушивает сообщения в compensation_queue. Получив сообщение, указывающее на сбой платежа, он выполняет компенсирующую транзакцию для отмены бронирования и возврата системы в согласованное состояние.


Вы успешно реализовали 3 микросервиса с помощью шаблона Saga. Каждый из них выполняет свои задачи и взаимодействует с другими сервисами через события.




Шаблон Saga и метод оркестрации


При использовании Saga на основе оркестрации (вместо Saga + хореография) необходим центральный координатор, сообщающий участвующим сервисам, какие локальные транзакции нужно выполнять.



Главные отличия шаблона Saga c методом оркестрации:



  • Всем процессом управляет отдельный сервис Orchestrator.

  • Каждый сервис взаимодействует с Оркестратором после завершения локальной транзакции.

  • Оркестратор принимает решение о выполнении очередного шага и отправляет соответствующему сервису команды, включая любые компенсирующие транзакции, необходимые в случае отказа.


Выбор между хореографией и оркестрацией часто определяется сложностью бизнес-процесса, задаваемой степенью взаимосвязи между сервисами и необходимостью централизованного контроля над бизнес-транзакциями. Более централизованный метод хореографии требует меньше настроек, а оркестрация может обеспечить больше контроля, упрощает управление и мониторинг сложных шаблонов Saga.




Заключение


В архитектуре микросервисов шаблон Saga позволяет эффективно решать проблемы распределенных транзакций, обеспечивая согласованность данных между независимыми сервисами. Грамотное применение шаблона Saga необходимо для создания надежных и отказоустойчивых приложений с микросервисами, которые сегодня наиболее востребованы.



40   0  

Comments

    Ничего не найдено.