How FTGO coordinates distributed transactions using the Saga pattern — covering CreateOrderSaga, CancelOrderSaga, and ReviseOrderSaga with compensating steps.
Use this file to discover all available pages before exploring further.
This page explains how the FTGO application implements the Saga pattern to coordinate multi-step business transactions that span multiple microservices. Without a distributed transaction coordinator, each step is a local database transaction; the saga orchestrator sequences them and triggers compensating transactions if any step fails.
Two-phase commit (2PC) requires all participating services to hold locks until the coordinator commits. In a microservices architecture this creates tight coupling, blocks availability, and is incompatible with NoSQL stores. Sagas replace a single distributed ACID transaction with a sequence of local transactions, each publishing a message or event that triggers the next step.
FTGO uses orchestration-based sagas via the Eventuate Tram Sagas library. A central orchestrator (the saga class) sends commands to participants over messaging channels and reacts to their replies.
CreateOrderSaga is the most detailed saga in FTGO. It validates the consumer, reserves a kitchen ticket, authorizes payment, then confirms both the ticket and the order.
The first step has no forward action — it registers a compensation command so that if any later step fails, the Order Service receives a RejectOrderCommand and marks the order REJECTED.
// CreateOrderSagaState.javaRejectOrderCommand makeRejectOrderCommand() { return new RejectOrderCommand(getOrderId());}
2
Validate Consumer
The Consumer Service checks whether the consumer is allowed to place orders of this total value. The orchestrator sends a ValidateOrderByConsumer command to the consumerService channel.
ValidateOrderByConsumer makeValidateOrderByConsumerCommand() { ValidateOrderByConsumer x = new ValidateOrderByConsumer(); x.setConsumerId(getOrderDetails().getConsumerId()); x.setOrderId(getOrderId()); x.setOrderTotal(getOrderDetails().getOrderTotal().asString()); return x;}
3
Create Kitchen Ticket
A CreateTicket command is sent to Kitchen Service. The reply carries the new ticketId, which the saga state stores for later compensation.
CreateTicket makeCreateTicketCommand() { return new CreateTicket( getOrderDetails().getRestaurantId(), getOrderId(), makeTicketDetails(getOrderDetails()));}void handleCreateTicketReply(CreateTicketReply reply) { setTicketId(reply.getTicketId());}// Compensation — sent if a later step failsCancelCreateTicket makeCancelCreateTicketCommand() { return new CancelCreateTicket(getOrderId());}
4
Authorize Payment
An AuthorizeCommand is sent to Accounting Service. This step has no compensation: if authorization fails the saga rolls back the kitchen ticket via the compensation registered in step 3.
AuthorizeCommand makeAuthorizeCommand() { return new AuthorizeCommand() .withConsumerId(getOrderDetails().getConsumerId()) .withOrderId(getOrderId()) .withOrderTotal(getOrderDetails().getOrderTotal().asString());}
5
Confirm Kitchen Ticket
Once payment is authorized, Kitchen Service is told to confirm the ticket using the ticketId stored from step 3.
ConfirmCreateTicket makeConfirmCreateTicketCommand() { return new ConfirmCreateTicket(getTicketId());}
6
Approve Order
The final step transitions the order from APPROVAL_PENDING to APPROVED.
ApproveOrderCommand makeApproveOrderCommand() { return new ApproveOrderCommand(getOrderId());}
CancelOrderSaga coordinates cancellation across the Order, Kitchen, and Accounting services. Steps 1 and 2 each have compensations to undo a partial cancel if the saga fails mid-way.
ReviseOrderSaga follows the same five-step structure. The first step replies with the revised order total so the saga can pass an accurate amount to Accounting Service in the authorization revision step.
If the Accounting Service finds the account disabled it replies with AccountDisabledReply, which the saga treats as a failure and triggers compensation for all preceding steps.
Compensation transactions run in reverse order when a step fails. Steps that do not have a compensation (e.g., authorizePayment) are considered pivot transactions — once they succeed, the saga is committed forward.