Hemos visto dos enfoques principales para coordinar una Saga: orquestación, cuando existe un coordinador centralizado, y coreografía, cuando los microservicios intercambian eventos directamente — como en una fiesta donde todos bailan sin DJ. En esta práctica nos meteremos en la implementación del patrón Saga en la práctica.
Caso de negocio: reserva de viaje
Supongamos que desarrollamos una aplicación para una agencia de viajes. El usuario quiere reservar un paquete "todo incluido": vuelo, hotel y excursión. Sin embargo, cada uno de esos pasos se solicita a través de microservicios separados:
- Flight Service — se encarga de la reserva de billetes de avión.
- Hotel Service — gestiona la reserva de habitaciones de hotel.
- Tour Service — gestiona las reservas de excursiones.
Nuestra tarea — implementar el proceso de reserva usando el patrón Saga, de modo que:
- Si todos los pasos tienen éxito, se confirme la reserva.
- Si hay fallo en cualquier etapa, revertimos los pasos previos (cancelar reservas de billetes, liberar habitaciones de hotel, etc.).
Diseño del sistema
Antes de meternos a escribir código, veamos cómo será el proceso a nivel de interacción entre microservicios. Elegiremos el enfoque de orquestación para coordinar el proceso más fácilmente.
Pasos de la Saga
- Iniciar la reserva.
- Reservar vuelo en Flight Service.
- Reservar hotel en Hotel Service.
- Reservar excursión en Tour Service.
- Confirmar la reserva si todo va bien.
- Cancelar todas las acciones previas en caso de error.
Diagrama de interacción
Saga Orchestrator
|
|---> Flight Service (Reserve)
| |
| ---> Failure? --> Cancel Flight
|
|---> Hotel Service (Reserve)
| |
| ---> Failure? --> Cancel Hotel
|
|---> Tour Service (Reserve)
| |
| ---> Failure? --> Cancel Tour
|
---> Booking Confirmed!
Acciones compensatorias
Cada acción en la Saga debe tener una forma de deshacerla. Por ejemplo, si la reserva del vuelo fue exitosa y la reserva del hotel falló, necesitamos cancelar la reserva del vuelo.
Implementación
Usaremos Spring Boot y Spring Kafka para implementar la Saga con un Orchestrator. Para simplificar, dejaremos nuestros microservicios como emuladores (por ejemplo, simples REST controllers).
Preparación del proyecto
Crearemos cuatro microservicios: Orchestrator, FlightService, HotelService, TourService.
Paso 1: Configurar Orchestrator
Empezamos creando el servicio Orchestrator para gobernar la Saga.
Añadimos dependencias en 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>
Creamos la estructura del proyecto:
.
├── 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.";
}
}
}
Paso 2: Crear los servicios FlightService, HotelService, TourService
FlightServiceClient.java
@Component
public class FlightServiceClient {
public String reserveFlight() {
// Emulación de la reserva
System.out.println("Reserving flight...");
return UUID.randomUUID().toString(); // devolvemos un ID ficticio
}
public void cancelFlight(String bookingId) {
System.out.println("Flight reservation with ID " + bookingId + " cancelled.");
}
}
De forma análoga, crea HotelServiceClient y TourServiceClient.
Añadir Kafka para la transmisión de eventos
Ahora, para que nuestros microservicios se comuniquen entre sí, integremos Kafka. Por ejemplo, FlightService enviará eventos de confirmación de reserva, y el Orchestrator los recogerá.
Configuración de 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: procesamiento de respuestas:
@KafkaListener(topics = "flight-status", groupId = "orchestrator")
public void listenFlightResponses(String message) {
System.out.println("Received flight status: " + message);
}
Hemos sentado las bases de una Saga con una gestión sencilla a través de microservicios. Los siguientes pasos incluyen añadir un manejo de errores más detallado y mejorar el registro mediante trazas distribuidas.
GO TO FULL VERSION