micro-module/broadway-saga
Micro Module Broadway Saga brings saga orchestration to Laravel applications using Broadway, helping you coordinate multi-step business workflows across domain events with event-sourced state and clean service-provider integration.
This package is a thin Laravel adapter around broadway/broadway's saga subsystem (broadway/saga). It does not give you event sourcing — it assumes you already have a Broadway EventBus and CommandBus bound in the container. Treat it as glue, not a framework.
Steps to get productive:
composer.json — confirms which broadway/* version it pins (this dictates the event/command API you code against).src/ — usually one ServiceProvider (e.g. BroadwaySagaServiceProvider) and a config file.config/ — where sagas are registered and where the saga-state repository is chosen.database/migrations/ (if present) — the saga_state table.composer require micro-module/broadway-saga
php artisan vendor:publish --provider="MicroModule\\BroadwaySaga\\BroadwaySagaServiceProvider"
php artisan migrate
app(SagaManager::class) or whatever the provider aliases) and confirm it is attached to the same event bus your domain publishes to. If your EventBus is a separate binding, the saga will silently never fire.OrderPlaced and issues a ReserveStock command, then listens for StockReserved/StockReservationFailed to either proceed or compensate:
class OrderSaga extends AbstractSaga
{
public function handleOrderPlaced(OrderPlaced $event): void
{
$this->commandBus->dispatch(new ReserveStock($event->orderId, $event->items));
}
}
Register it in config/broadway-saga.php (or the provider's saga list), migrate, and publish a test event. If nothing happens, that's the event-bus binding issue above, not your saga.Saga-per-process, correlated by ID. Give each saga a stable getSagaId() derived from the aggregate/order ID. The manager uses it to load the persisted state before handing the event to the handler, so a second OrderPlaced for the same order resumes rather than replays.
Command dispatch from handlers. Sagas should only react and dispatch commands — keep business rules in command handlers, keep the saga as orchestration. Long if/else inside a saga handler is the first smell.
Compensation with explicit state flags. Persist step markers on the saga (or rely on SagaState) so a failure event can branch to a compensating command. This is the whole point of using a saga over a plain event listener.
Registering sagas.
sagas => [OrderSaga::class, ...]) is the normal path in these adapters; resolve via the container so dependencies inject.SagaManager::addSaga(), you can also register in a boot() method in your own provider — useful when saga registration needs runtime config.Async processing. If your Broadway event bus is queued, sagas run in the worker. Ensure the saga-state repository is a real database repository (not in-memory) so state survives between jobs. Bind your own SagaRepository if the package's default is not durable.
Testing with Scenario. Broadway ships Broadway\Saga\Testing\Scenario:
Scenario::forSaga(new OrderSaga($commandBus))
->when(new OrderPlaced(...))
->then(new ReserveStock(...));
This is the fastest feedback loop and avoids booting Laravel at all — use it before the integration test.
"micro-module/broadway-saga": "1.0.*" style) and budget to fork. The broadway/* API changed across 0.x/1.x, so a fresh composer require today can pull a Broadway version the adapter's reflection-based handler dispatch doesn't match.getSagaId() collides, or the event class name doesn't match the handler method (handleOrderPlaced for OrderPlaced), nothing throws — the event is just ignored. Add a log line at the top of every handler while bringing a saga up.app()->bound() / the container alias and ensure it's the same singleton.InMemorySagaRepository for convenience. It works in tests and breaks in production (state lost per request/worker). Explicitly configure a DB-backed repository and confirm the migration ran.SagaRepository binding (swap for your own table/ORM), the saga registry in config, and the SagaManager construction if you need to attach extra middleware. These are the three seams in a package this small.EventBus and CommandBus resolvable, event store migrated). This adapter has almost no logic of its own — debug Broadway first, then the registration.How can I help you explore Laravel packages today?