broadway/broadway-saga
Broadway Saga adds saga/process manager support to the Broadway event-sourcing framework. Coordinate long-running business workflows across bounded contexts, reacting to domain events and dispatching commands to drive eventual consistency in CQRS/ES applications.
Installation
composer require broadway/broadway-saga
Ensure broadway/broadway is also installed (required dependency).
Register the Service Provider
Add to config/app.php under providers:
Broadway\Saga\SagaServiceProvider::class,
Publish Configuration
php artisan vendor:publish --provider="Broadway\Saga\SagaServiceProvider"
Config file: config/broadway-saga.php.
Define a Saga
Create a class implementing Broadway\Saga\Saga:
use Broadway\Saga\Saga;
class OrderSaga implements Saga
{
public function __invoke()
{
// Saga logic here
}
}
First Use Case: Trigger a Saga
Use the SagaManager to start a saga:
$sagaManager = app(SagaManager::class);
$saga = $sagaManager->create(OrderSaga::class);
$sagaManager->start($saga);
src/Saga.php (interface definition).src/SagaManager.php (core logic for saga orchestration).src/Event/SagaStarted.php (event triggers).config/broadway-saga.php (adjust retry policies, event dispatching, etc.).Define Saga Steps
Break saga logic into discrete steps (e.g., validateOrder, chargePayment, fulfillOrder):
class OrderSaga implements Saga
{
public function __invoke()
{
$this->validateOrder();
$this->chargePayment();
$this->fulfillOrder();
}
private function validateOrder() { /* ... */ }
private function chargePayment() { /* ... */ }
private function fulfillOrder() { /* ... */ }
}
Handle Failures with Compensating Transactions
Implement CompensatingTransaction for rollback logic:
use Broadway\Saga\CompensatingTransaction;
class ChargePaymentCompensation implements CompensatingTransaction
{
public function __invoke() { /* Refund logic */ }
}
// In saga:
$this->chargePaymentWithCompensation(
fn() => $this->chargePayment(),
ChargePaymentCompensation::class
);
Event-Driven Triggers Use Broadway events to kick off sagas:
// In an event subscriber:
public function handle(OrderPlaced $event)
{
$sagaManager = app(SagaManager::class);
$saga = $sagaManager->create(OrderSaga::class, ['orderId' => $event->orderId]);
$sagaManager->start($saga);
}
Leverage Broadway’s Event Store Store saga state in the event store for replayability:
$sagaManager->setEventStore($eventStore); // Inject in provider
Dependency Injection Bind saga dependencies in the service provider:
$this->app->bind(OrderSaga::class, function ($app) {
return new OrderSaga(
$app->make(PaymentGateway::class),
$app->make(OrderRepository::class)
);
});
Testing Sagas
Use SagaTestCase (if provided) or mock the SagaManager:
$sagaManager = Mockery::mock(SagaManager::class);
$sagaManager->shouldReceive('start')->once();
Retry Policies
Configure retries in config/broadway-saga.php:
'retry_policy' => [
'max_attempts' => 3,
'delay' => 100, // ms
'multiplier' => 2,
],
State Management
Circular Dependencies
Event Ordering
Compensation Gaps
Enable Saga Logging
Add to config/broadway-saga.php:
'logging' => [
'enabled' => true,
'channel' => 'single',
],
Inspect Saga State Query the event store for saga events:
php artisan tinker
>>> $eventStore->load('saga-id');
Common Exceptions
SagaAlreadyStartedException: Saga was already in progress.
Fix: Use saga IDs to avoid duplicates.CompensationFailedException: Compensating transaction failed.
Fix: Implement retry logic or alerting.Custom Saga States
Extend Broadway\Saga\SagaState to add metadata:
class CustomSagaState extends SagaState
{
public $customField;
}
Event Dispatching
Override SagaManager to dispatch custom events:
$sagaManager->setEventDispatcher($customDispatcher);
Saga Factories Create factories for complex saga initialization:
$saga = app(SagaFactory::class)->create(OrderSaga::class, $data);
Middleware for Sagas Add middleware to sagas (e.g., logging, auth):
$sagaManager->addMiddleware(new LogSagaMiddleware());
How can I help you explore Laravel packages today?