CodeGym /Courses /Module 5. Spring /Lecture 215: Hands-on: implementing the Saga pattern for ...

Lecture 215: Hands-on: implementing the Saga pattern for a distributed process

Module 5. Spring
Level 14 , Lesson 4
Available

We've covered the two main approaches to coordinating a Saga: orchestration, where there's a centralized coordinator, and choreography, where microservices exchange events directly — like a party where everyone dances without a DJ. In this hands-on session we'll dive into implementing the Saga pattern in practice.

Business case: travel booking

Suppose we're building an app for a travel agency. The user wants to book an "all-inclusive" package: flight, hotel, and tour. However, each of these steps is handled by a separate microservice:

  • Flight Service — responsible for booking flights.
  • Hotel Service — handles hotel room bookings.
  • Tour Service — manages tour orders.

Our task is to implement the booking process using the Saga pattern so that:

  1. If all steps succeed, we confirm the booking.
  2. If any step fails, we roll back previous steps (cancel flight bookings, release hotel rooms, etc.).

System design

Before we dive into code, let's outline how the process will look at the microservice interaction level. We'll pick the orchestration approach to make coordination simpler.

Saga steps

  1. Start the booking.
  2. Reserve flights in Flight Service.
  3. Reserve a hotel in Hotel Service.
  4. Reserve a tour in Tour Service.
  5. Complete the booking on success.
  6. Cancel all previously completed actions if an error occurs.

Interaction diagram


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

Compensating actions

Every action in a Saga should have a compensating action. For example, if the flight booking succeeded but the hotel booking failed, we need to cancel the flight reservation.


Implementation

We'll use Spring Boot and Spring Kafka to implement the Saga with an orchestrator. For simplicity, we'll keep our microservices as emulators (for example, simple REST controllers).

Preparing the project

Let's create four microservices: Orchestrator, FlightService, HotelService, TourService.

Step 1: Setting up the Orchestrator

We'll start by creating the Orchestrator service to manage the saga.

Add dependencies to 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>

Create the project structure:

.
├── 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.";
        }
    }
}

Step 2: Creating FlightService, HotelService, TourService

FlightServiceClient.java


@Component
public class FlightServiceClient {

    public String reserveFlight() {
        // Emulate booking
        System.out.println("Reserving flight...");
        return UUID.randomUUID().toString(); // return a dummy ID
    }

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

Similarly, create HotelServiceClient and TourServiceClient.


Adding Kafka for event delivery

Now, so our microservices can talk to each other, let's integrate Kafka. For example, FlightService will send booking confirmation events, and the Orchestrator will pick them up.

Setting up 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: handling responses:

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

We've built the basics of a Saga with simple orchestration across microservices. Next steps include adding more detailed error handling and improving logging with distributed tracing.

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION