JavaRush /Курсы /Модуль 5. Spring /Лекция 215: Практика: реализация паттерна саги для распре...

Лекция 215: Практика: реализация паттерна саги для распределённого процесса

Модуль 5. Spring
22 уровень , 4 лекция
Открыта

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

Бизнес-кейс: бронирование путешествия

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

  • Flight Service — отвечает за бронирование авиабилетов.
  • Hotel Service — обрабатывает бронирование номеров в гостинице.
  • Tour Service — управляет заказом экскурсий.

Наша задача — реализовать процесс бронирования через паттерн Saga, чтобы:

  1. При успехе всех шагов мы подтверждали бронирование.
  2. При неудаче на любом этапе мы откатывали предыдущие шаги (отменяли бронирование билетов, освобождали гостиничные номера и т.д.).

Проектирование системы

Перед тем как погрузиться в написание кода, давайте разберём, как будет выглядеть процесс на уровне взаимодействия микросервисов. Мы выберем подход оркестрации, чтобы проще координировать процесс.

Шаги Saga

  1. Начать бронирование.
  2. Забронировать авиабилеты в Flight Service.
  3. Забронировать гостиницу в Hotel Service.
  4. Забронировать экскурсию в Tour Service.
  5. Завершить бронирование при успехе.
  6. Отменить все ранее выполненные действия в случае ошибки.

Диаграмма взаимодействия


Saga Orchestrator
   |
   |---> Flight Service (Reserve)
   |       |
   |       ---> Failure? --> Cancel Flight
   |
   |---> Hotel Service (Reserve)
   |       |
   |       ---> Failure? --> Cancel Hotel
   |
   |---> Tour Service (Reserve)
   |       |
   |       ---> Failure? --> Cancel Tour
   |
   ---> Booking Confirmed!

Компенсационные действия

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


Реализация

Мы будем использовать Spring Boot и Spring Kafka для реализации саги с оркестратором. Для простоты оставим наши микросервисы в виде эмуляторов (например, простых REST-контроллеров).

Подготовка проекта

Создадим три микросервиса: Orchestrator, FlightService, HotelService, TourService.

Шаг 1: Настройка Orchestrator

Начнем с создания сервиса Orchestrator для управления сагой.

Добавляем зависимости в pom.xml:


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>

Создаем структуру проекта:

.
├── OrchestratorApplication.java
└── services/
    ├── FlightServiceClient.java
    ├── HotelServiceClient.java
    └── TourServiceClient.java

SagaOrchestrator.java


@Service
public class SagaOrchestrator {

    private final FlightServiceClient flightService;
    private final HotelServiceClient hotelService;
    private final TourServiceClient tourService;

    public SagaOrchestrator(FlightServiceClient flightService, HotelServiceClient hotelService, TourServiceClient tourService) {
        this.flightService = flightService;
        this.hotelService = hotelService;
        this.tourService = tourService;
    }

    public String processBooking() {
        try {
            // Step 1: book flight
            String flightBookingId = flightService.reserveFlight();
            System.out.println("Flight reserved with ID: " + flightBookingId);

            // Step 2: book hotel
            String hotelBookingId = hotelService.reserveHotel();
            System.out.println("Hotel reserved with ID: " + hotelBookingId);

            // Step 3: book tour
            String tourBookingId = tourService.reserveTour();
            System.out.println("Tour reserved with ID: " + tourBookingId);

            return "Booking successful!";
        } catch (Exception e) {
            System.err.println("Booking failed: " + e.getMessage());
            return "Booking failed and rolled back.";
        }
    }
}

Шаг 2: Создание сервисов FlightService, HotelService, TourService

FlightServiceClient.java


@Component
public class FlightServiceClient {

    public String reserveFlight() {
        // Эмуляция бронирования
        System.out.println("Reserving flight...");
        return UUID.randomUUID().toString(); // возвращаем фиктивный ID
    }

    public void cancelFlight(String bookingId) {
        System.out.println("Flight reservation with ID " + bookingId + " cancelled.");
    }
}

По аналогии создайте HotelServiceClient и TourServiceClient.


Добавление Kafka для передачи событий

Теперь, чтобы наши микросервисы общались между собой, давайте интегрируем Kafka. Например, FlightService будет отправлять события о подтверждении брони, а Orchestrator подхватывать это.

Настройка KafkaProducer:


@Service
public class KafkaProducer {

    private final KafkaTemplate<String, String> kafkaTemplate;

    public KafkaProducer(KafkaTemplate<String, String> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void sendEvent(String topic, String message) {
        kafkaTemplate.send(topic, message);
        System.out.println("Event sent to topic: " + topic + ", message: " + message);
    }
}

Orchestrator: обработка ответов:

@KafkaListener(topics = "flight-status", groupId = "orchestrator")
public void listenFlightResponses(String message) {
    System.out.println("Received flight status: " + message);
}

Мы построили основы Saga с простым управлением через микросервисы. Дальнейшие шаги включают добавление детализированной обработки ошибок и улучшение логирования через распределённые трассировки.

Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Кирилл Уровень 104
29 января 2026
В коде класса SagaOrchestrator не использует KafkaProducer. Метод listenFlightResponses (и другие возможные слушатели) тоже не вызываются внутри этого класса. Так и задумано?
Кирилл Уровень 104
29 января 2026
"Создадим три микросервиса: Orchestrator, FlightService, HotelService, TourService" - микросервиса действительно 3. Но класса указано 4. Не повредит, если это предложение изложат по другому