Мы изучили два основных подхода к координации Saga: оркестрация, когда есть централизованный координатор, и хореография, когда микросервисы обмениваются событиями напрямую — как на вечеринке, где все пляшут без диджея. В этом практическом занятии мы погрузимся в реализацию паттерна Saga на практике.
Бизнес-кейс: бронирование путешествия
Предположим, мы разрабатываем приложение для туристического агентства. Пользователь хочет забронировать пакет "всё включено": перелёт, гостиницу и экскурсию. Однако каждый из этих шагов заказывается через отдельные микросервисы:
- Flight Service — отвечает за бронирование авиабилетов.
- Hotel Service — обрабатывает бронирование номеров в гостинице.
- Tour Service — управляет заказом экскурсий.
Наша задача — реализовать процесс бронирования через паттерн Saga, чтобы:
- При успехе всех шагов мы подтверждали бронирование.
- При неудаче на любом этапе мы откатывали предыдущие шаги (отменяли бронирование билетов, освобождали гостиничные номера и т.д.).
Проектирование системы
Перед тем как погрузиться в написание кода, давайте разберём, как будет выглядеть процесс на уровне взаимодействия микросервисов. Мы выберем подход оркестрации, чтобы проще координировать процесс.
Шаги Saga
- Начать бронирование.
- Забронировать авиабилеты в Flight Service.
- Забронировать гостиницу в Hotel Service.
- Забронировать экскурсию в Tour Service.
- Завершить бронирование при успехе.
- Отменить все ранее выполненные действия в случае ошибки.
Диаграмма взаимодействия
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 с простым управлением через микросервисы. Дальнейшие шаги включают добавление детализированной обработки ошибок и улучшение логирования через распределённые трассировки.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ