Back to Blog
Saga Pattern: Managing Distributed Transactions
The Problem with Distributed Transactions
In distributed systems, traditional ACID transactions don't work across service boundaries. The saga pattern provides eventual consistency through a sequence of local transactions with compensating actions for failures.
Saga Types
Choreography
// Services react to events
class OrderCreated { }
// → Inventory reserves stock → StockReserved
// → Payment charges card → PaymentCompleted
// → Shipping creates shipment → ShipmentCreated
class StockReservedHandler
{
public function handle(StockReserved $event): void
{
// Trigger next step
event(new ProcessPayment($event->orderId));
}
}
Orchestration
class OrderSaga
{
private array $completedSteps = [];
public function execute(Order $order): void
{
try {
$this->reserveInventory($order);
$this->completedSteps[] = 'inventory';
$this->processPayment($order);
$this->completedSteps[] = 'payment';
$this->createShipment($order);
$this->completedSteps[] = 'shipment';
$this->completeOrder($order);
} catch (Exception $e) {
$this->compensate($order);
throw $e;
}
}
private function compensate(Order $order): void
{
foreach (array_reverse($this->completedSteps) as $step) {
match ($step) {
'shipment' => $this->cancelShipment($order),
'payment' => $this->refundPayment($order),
'inventory' => $this->releaseInventory($order),
};
}
}
}
Saga State Management
class SagaState extends Model
{
protected $casts = [
'completed_steps' => 'array',
'failed_at' => 'datetime',
];
public function markStepComplete(string $step): void
{
$steps = $this->completed_steps ?? [];
$steps[] = $step;
$this->update(['completed_steps' => $steps]);
}
public function markFailed(string $reason): void
{
$this->update([
'status' => 'failed',
'failure_reason' => $reason,
'failed_at' => now(),
]);
}
}
Idempotency
class IdempotentHandler
{
public function handle(ProcessPayment $command): void
{
$key = "payment_{$command->orderId}";
if (Cache::has($key)) {
return; // Already processed
}
$this->payment->charge($command->amount);
Cache::put($key, true, now()->addDays(7));
}
}
Conclusion
Sagas provide eventual consistency in distributed systems. Choose choreography for simple flows, orchestration for complex ones. Always implement compensating actions and ensure idempotency.
Related Articles
Need Help With Your Project?
I respond to all inquiries within 24 hours. Let's discuss how I can help build your production-ready system.
Get In Touch