Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Saga Laravel Package

brzuchal/saga

Laravel package implementing the Saga pattern for coordinating long-running, distributed workflows. Helps model multi-step processes with compensating actions, track saga state, and handle failures/retries so complex business transactions stay consistent across services.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require brzuchal/saga
    

    Add the service provider to config/app.php:

    'providers' => [
        // ...
        Brzuchal\Saga\SagaServiceProvider::class,
    ],
    
  2. Basic Configuration Publish the config file:

    php artisan vendor:publish --provider="Brzuchal\Saga\SagaServiceProvider" --tag="saga-config"
    

    Update config/saga.php with your preferred storage (e.g., database, redis).

  3. First Use Case: Simple Saga Define a saga class:

    namespace App\Sagas;
    
    use Brzuchal\Saga\Saga;
    
    class OrderProcessingSaga extends Saga
    {
        public function execute()
        {
            $this->validateOrder();
            $this->processPayment();
            $this->fulfillOrder();
        }
    
        protected function validateOrder()
        {
            // Logic
        }
    
        protected function processPayment()
        {
            // Logic
        }
    
        protected function fulfillOrder()
        {
            // Logic
        }
    }
    
  4. Trigger the Saga

    use App\Sagas\OrderProcessingSaga;
    
    $saga = new OrderProcessingSaga(['order_id' => 123]);
    $saga->run();
    

Implementation Patterns

Workflow Integration

  1. Event-Driven Triggers Use Laravel events to kick off sagas:

    // In an event listener
    event(new OrderCreated($order));
    
    // In a listener
    public function handle(OrderCreated $event)
    {
        $saga = new OrderProcessingSaga(['order_id' => $event->order->id]);
        $saga->run();
    }
    
  2. Command-Based Execution Create an Artisan command for manual triggering:

    namespace App\Console\Commands;
    
    use App\Sagas\OrderProcessingSaga;
    use Illuminate\Console\Command;
    
    class ProcessOrderSaga extends Command
    {
        protected $signature = 'saga:process-order {order_id}';
        public function handle()
        {
            $saga = new OrderProcessingSaga(['order_id' => $this->argument('order_id')]);
            $saga->run();
        }
    }
    
  3. Compensation Handling Implement rollback logic in saga steps:

    protected function processPayment()
    {
        try {
            // Payment logic
        } catch (\Exception $e) {
            $this->compensate('refundPayment');
            throw $e;
        }
    }
    
    protected function compensateRefundPayment()
    {
        // Refund logic
    }
    

State Management

  • Track Saga State Use the built-in state machine:

    $this->setState('validated'); // Manually set state
    $this->transitionTo('processing'); // Transition to next state
    
  • Conditional Logic Use state checks to control flow:

    if ($this->getState() === 'paid') {
        $this->fulfillOrder();
    }
    

Persistence

  • Database Storage Configure config/saga.php:

    'storage' => [
        'driver' => 'database',
        'table' => 'sagas',
    ],
    

    Migrate the table:

    php artisan vendor:publish --provider="Brzuchal\Saga\SagaServiceProvider" --tag="migrations"
    php artisan migrate
    
  • Redis Storage For high-performance environments:

    'storage' => [
        'driver' => 'redis',
        'connection' => 'cache',
    ],
    

Gotchas and Tips

Common Pitfalls

  1. Transaction Boundaries

    • Issue: Saga steps may not be atomic if not wrapped in transactions.
    • Fix: Use Laravel transactions for database operations:
      DB::transaction(function () {
          $this->processPayment();
      });
      
  2. State Inconsistency

    • Issue: Manual state changes can lead to inconsistencies.
    • Fix: Prefer transitionTo() over setState() to enforce valid state transitions.
  3. Error Handling

    • Issue: Uncaught exceptions in saga steps may leave sagas in limbo.
    • Fix: Wrap steps in try-catch blocks and log errors:
      try {
          $this->processPayment();
      } catch (\Exception $e) {
          Log::error("Payment failed for saga {$this->getId()}", ['error' => $e->getMessage()]);
          $this->compensate('refundPayment');
          throw $e;
      }
      
  4. Idempotency

    • Issue: Retried sagas may cause duplicate side effects.
    • Fix: Design sagas to be idempotent or use saga IDs to skip reprocessing:
      if ($this->getState() === 'completed') {
          return;
      }
      

Debugging Tips

  1. Log Saga Execution Enable logging in config/saga.php:

    'logging' => true,
    

    Check logs at storage/logs/laravel.log.

  2. Inspect Saga State Query the storage table directly:

    SELECT * FROM sagas WHERE saga_id = 'your-saga-id';
    
  3. Test Locally Use the saga:replay command to debug failed sagas:

    php artisan saga:replay your-saga-id
    

Extension Points

  1. Custom Storage Implement a custom storage driver by extending Brzuchal\Saga\Contracts\SagaStorage.

  2. Event Dispatching Extend the saga to dispatch events at key steps:

    protected function processPayment()
    {
        event(new PaymentProcessed($this->getData()));
        // ...
    }
    
  3. Middleware Add middleware to sagas for cross-cutting concerns (e.g., auth, logging):

    use Brzuchal\Saga\Contracts\SagaMiddleware;
    
    class LoggingMiddleware implements SagaMiddleware
    {
        public function handle($saga, $next)
        {
            Log::info("Saga {$saga->getId()} started");
            $next($saga);
            Log::info("Saga {$saga->getId()} completed");
        }
    }
    

    Register in config/saga.php:

    'middleware' => [
        \App\Middleware\LoggingMiddleware::class,
    ],
    
  4. Retry Logic Implement exponential backoff for transient failures:

    protected function processPayment()
    {
        $attempts = 0;
        while ($attempts < 3) {
            try {
                // Payment logic
                return;
            } catch (\Exception $e) {
                $attempts++;
                if ($attempts === 3) throw $e;
                sleep(2 ** $attempts); // Exponential backoff
            }
        }
    }
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
besmartand-pro/php-quality-config
sentix/ai-chatbot
codifyo/ts-generator-bundle
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky
spatie/mailcoach-vapor