Distributed Transactions with Sagas

Motivation

Most businesses have to deal with managing complex monetary transactions and transfers, including:

  • Handling consumer's subscriptions, installment payments, and communications in a reliable and timely manner.
  • Integrating with multiple payment systems and shopping platform backends.
  • Detecting suspicious and fraudulent Activities.

Similar to microservices orchestration, such workflows need a way to deliver the transactional consistency—but across multiple third-party vendors. Each of these third-party systems has a potential for failure, delays, or intermittent availability issues. Despite the challenges, the entire process represents a long-running transaction that needs to eventually complete in a predictable way.

In some cases, instead of trying to complete the process by continuously retrying, compensation rollback logic should be executed. Saga Pattern is one way to standardize compensation APIs.

Benefits of Temporal

Temporal provides an extensive toolset for dealing with the unpredictability of external services via reliable and transparent mechanisms: built-in execution guarantees, exponential Activity retries, timeouts.

Temporal boasts native Saga Pattern support out of the box. Simply define a compensation action for each Workflow Activity. That way, when a failure happens in one of the downstream services, compensation actions will run for each of the Activities that previously succeeded.

Example

The Workflow snippet below orchestrates two Activity calls: booking a hotel and reserving a flight. If the first Activity fails (including all the configured retries), the Workflow returns directly.

However, if the first Activity (reservation) succeeds, but the second one fails, you need to cancel the already booked hotel to avoid undesirable charges. The error-handling block contains a call that cancels the hotel reservation before completing the Workflow.

Saga.Options sagaOptions = new Saga.Options.Builder().build();
Saga saga = new Saga(sagaOptions);
try {
// Step 1: Book a hotel.
String hotelReservationID = activities.bookHotel(name);
saga.addCompensation(activities::cancelHotel, hotelReservationID, name);
// Step 2: Book a flight.
String flightReservationID = activities.bookFlight(name);
saga.addCompensation(activities::cancelFlight, flightReservationID, name);
// Both reservations succeeded.
} catch (ActivityException e) {
saga.compensate();
throw e;
}

Next Steps