Problem Statement
Design an Orchestrator-based Saga Pattern engine for distributed transactions (similar to execution coordinators in modern microservice architectures). The engine is responsible for executing a sequence of steps across multiple remote services (e.g., Order creation, Payment deduction, Inventory reservation). If any step in the sequence fails, the orchestrator must rollback the transaction by executing compensatory commands in reverse order for all previously completed steps.
Design Decisions & Patterns Used
Managing transactions across independent databases in microservices is highly complex. Since we cannot use database-level two-phase commit (2PC) locks without bottlenecking performance, we apply the Saga Pattern. In an orchestrator-based Saga, a central component coordinates the workflow steps. If a step fails, the orchestrator executes compensations in reverse order (e.g., if step 3 fails, it executes compensation for step 2, then step 1).
We will utilize the following Design Patterns:
- Command Pattern: Modeling each service step as a transaction command exposing two behaviors:
execute()(forward progress) andcompensate()(rollback). - State Pattern: Tracking transaction lifecycle states (e.g.,
SUCCESSFUL,FAILED,COMPENSATING) to determine transition rules. - Template Method: Defining the high-level transactional execution skeleton while letting subclasses implement service-specific calls.
Functional Requirements
- Define steps with forward execution and backward compensation routines.
- Coordinate transaction state transitions:
SUCCESSFUL,FAILED,IN_PROGRESS,COMPENSATING. - Execute transaction steps sequentially. Abort immediately if any step fails.
- Trigger compensations in reverse order for all completed steps when a failure occurs.
- Ensure the orchestrator can handle execution retries if a compensation fails.
Objects Required
SagaStatus(Enum tracking execution states)SagaStep(Interface representing a workflow command)OrderCreationStep,PaymentProcessingStep,InventoryReservationStep(Concrete steps)Saga(Model representing a workflow pipeline)SagaOrchestrator(Execution engine coordinating transitions and rollbacks)
SagaStatus Enum & SagaStep Interface
The SagaStatus tracks the transactional state, and the SagaStep interface defines the commands required for forward and backward operations.
public enum SagaStatus {
SUCCESSFUL,
FAILED,
IN_PROGRESS,
COMPENSATING
}
Let's define the SagaStep interface:
public interface SagaStep {
boolean execute();
boolean compensate();
String getName();
}
The execute() method runs the forward progress logic, returning a boolean indicating success. compensate() runs the rollback logic to reverse the execution if a subsequent step fails.
Concrete Service Steps (Commands)
We implement three mock steps to simulate distributed services: OrderCreationStep, PaymentProcessingStep, and InventoryReservationStep.
public class OrderCreationStep implements SagaStep {
@Override
public boolean execute() {
System.out.println("[Order Service] Creating temporary order... SUCCESS");
return true;
}
@Override
public boolean compensate() {
System.out.println("[Order Service] Deleting order records... ROLLBACK SUCCESS");
return true;
}
@Override
public String getName() { return "Order Creation"; }
}
The OrderCreationStep represents the initial step in the transaction. Its compensation deletes the temporary order.
public class PaymentProcessingStep implements SagaStep {
private final boolean shouldSucceed;
public PaymentProcessingStep(boolean shouldSucceed) {
this.shouldSucceed = shouldSucceed;
}
@Override
public boolean execute() {
if (shouldSucceed) {
System.out.println("[Payment Service] Charging customer credit card... SUCCESS");
return true;
} else {
System.out.println("[Payment Service] Charging customer credit card... FAILED");
return false;
}
}
@Override
public boolean compensate() {
System.out.println("[Payment Service] Refunding charge to customer... ROLLBACK SUCCESS");
return true;
}
@Override
public String getName() { return "Payment Processing"; }
}
The PaymentProcessingStep constructor allows simulating authorization failures. Its compensation refunds the customer.
public class InventoryReservationStep implements SagaStep {
private final boolean shouldSucceed;
public InventoryReservationStep(boolean shouldSucceed) {
this.shouldSucceed = shouldSucceed;
}
@Override
public boolean execute() {
if (shouldSucceed) {
System.out.println("[Inventory Service] Blocking item stock... SUCCESS");
return true;
} else {
System.out.println("[Inventory Service] Blocking item stock... FAILED");
return false;
}
}
@Override
public boolean compensate() {
System.out.println("[Inventory Service] Releasing item stock block... ROLLBACK SUCCESS");
return true;
}
@Override
public String getName() { return "Inventory Reservation"; }
}
The InventoryReservationStep blocks stock in the forward path, and releases the stock block as its compensation.
Saga Class
The Saga class acts as the data model container holding the sequence of steps, tracking progress, and updating the transaction status.
import java.util.ArrayList;
import java.util.List;
public class Saga {
private final List<SagaStep> steps;
private SagaStatus status;
private int currentStepIndex;
public Saga() {
this.steps = new ArrayList<>();
this.status = SagaStatus.IN_PROGRESS;
this.currentStepIndex = 0;
}
public void addStep(SagaStep step) {
steps.add(step);
}
public List<SagaStep> getSteps() { return steps; }
public synchronized SagaStatus getStatus() { return status; }
public synchronized void setStatus(SagaStatus status) { this.status = status; }
public synchronized int getCurrentStepIndex() { return currentStepIndex; }
public synchronized void setCurrentStepIndex(int index) { this.currentStepIndex = index; }
}
Getter and setter methods are synchronized to ensure thread safety when the orchestrator evaluates or updates status metrics.
SagaOrchestrator Class
The SagaOrchestrator contains the workflow execution engine. It loops forward through steps, and triggers compensations in reverse order if a step fails.
public class SagaOrchestrator {
public boolean execute(Saga saga) {
System.out.println("Starting Saga Transaction Workflow execution...");
saga.setStatus(SagaStatus.IN_PROGRESS);
for (int i = 0; i < saga.getSteps().size(); i++) {
SagaStep step = saga.getSteps().get(i);
saga.setCurrentStepIndex(i);
System.out.println("Executing step: " + step.getName());
boolean success = step.execute();
if (!success) {
System.out.println("Step " + step.getName() + " failed. Triggering rollback...");
rollback(saga);
return false;
}
}
saga.setStatus(SagaStatus.SUCCESSFUL);
System.out.println("Saga Transaction Workflow completed successfully.");
return true;
}
private void rollback(Saga saga) {
saga.setStatus(SagaStatus.COMPENSATING);
// Iterate backwards from the failed step index
int failedIndex = saga.getCurrentStepIndex();
for (int i = failedIndex - 1; i >= 0; i--) {
SagaStep step = saga.getSteps().get(i);
System.out.println("Compensating step: " + step.getName());
boolean compensationSuccess = false;
int retries = 3;
// Retry loop for compensation errors
while (!compensationSuccess && retries > 0) {
compensationSuccess = step.compensate();
if (!compensationSuccess) {
retries--;
System.err.println("Compensate failed for " + step.getName() + ". Retrying... (" + retries + " left)");
}
}
if (!compensationSuccess) {
System.err.println("CRITICAL: Compensation failed for " + step.getName() + " after retries. Alert triggered.");
}
}
saga.setStatus(SagaStatus.FAILED);
System.out.println("Saga Transaction Workflow rollback completed.");
}
}
Here is an explanation of the core operations in the SagaOrchestrator class:
execute()sets the status toIN_PROGRESS, executes steps sequentially, and updates the step index. If a step returns false, it immediately callsrollback().rollback()transitions the status toCOMPENSATING, loops backward from the failed step index, and executes compensations. It includes a retry loop to handle compensation errors and logs alerts if retries fail.
Main Driver Class
This class tests our Saga orchestrator by simulating two scenarios: a successful transaction, and a payment failure that triggers a rollback.
public class Main {
public static void main(String[] args) {
SagaOrchestrator orchestrator = new SagaOrchestrator();
System.out.println("==========================================");
System.out.println("Scenario 1: Testing Successful Saga");
System.out.println("==========================================");
Saga successSaga = new Saga();
successSaga.addStep(new OrderCreationStep());
successSaga.addStep(new PaymentProcessingStep(true)); // simulate success
successSaga.addStep(new InventoryReservationStep(true)); // simulate success
orchestrator.execute(successSaga);
System.out.println("Saga Status: " + successSaga.getStatus());
System.out.println("\n==========================================");
System.out.println("Scenario 2: Testing Failed Saga (Payment Fails)");
System.out.println("==========================================");
Saga failedSaga = new Saga();
failedSaga.addStep(new OrderCreationStep());
failedSaga.addStep(new PaymentProcessingStep(false)); // simulate payment failure
failedSaga.addStep(new InventoryReservationStep(true));
orchestrator.execute(failedSaga);
System.out.println("Saga Status: " + failedSaga.getStatus());
}
}
The main() driver configures workflows, executes both scenarios, and outputs transaction steps and rollback operations to verify the orchestrator's behavior.
Comments
Post a Comment