How to Implement the Saga Pattern in Go

Web
Implement the Saga Pattern in Go by defining a coordinator that executes steps sequentially and triggers compensating actions on failure to ensure data consistency.

Implement the Saga Pattern in Go by defining a coordinator service that orchestrates steps and compensating actions for each step. Use a Saga struct to hold the sequence of actions and their compensations, then execute them sequentially while catching errors to trigger rollbacks.

package main

import (
	"fmt"
	"log"
)

type Step func() error
type Compensation func() error

type SagaStep struct {
	Action       Step
	Compensation Compensation
}

type Saga struct {
	Steps []SagaStep
}

func (s *Saga) Execute() error {
	for i, step := range s.Steps {
		if err := step.Action(); err != nil {
			log.Printf("Step %d failed, rolling back", i)
			return s.Rollback(i - 1)
		}
	}
	return nil
}

func (s *Saga) Rollback(fromIndex int) error {
	for i := fromIndex; i >= 0; i-- {
		if err := s.Steps[i].Compensation(); err != nil {
			return fmt.Errorf("rollback step %d failed: %w", i, err)
		}
	}
	return nil
}

func main() {
	saga := &Saga{
		Steps: []SagaStep{
			{
				Action: func() error { fmt.Println("Step 1: Create Order"); return nil },
				Compensation: func() error { fmt.Println("Compensate 1: Cancel Order"); return nil },
			},
			{
				Action: func() error { fmt.Println("Step 2: Reserve Inventory"); return fmt.Errorf("inventory unavailable") },
				Compensation: func() error { fmt.Println("Compensate 2: Release Inventory"); return nil },
			},
			{
				Action: func() error { fmt.Println("Step 3: Charge Payment"); return nil },
				Compensation: func() error { fmt.Println("Compensate 3: Refund Payment"); return nil },
			},
		},
	}

	if err := saga.Execute(); err != nil {
		log.Printf("Saga failed: %v", err)
	}
}