micro-module/broadway
Broadway is a modular admin panel package for Laravel, offering dashboards, menus, user and permission management out of the box. Build back-office interfaces quickly and extend them with your own modules.
composer require micro-module/broadway
Before writing any application code, read the package source. It is a thin Laravel wrapper around broadway/broadway, so the authoritative documentation for the core concepts (AggregateRoot, CommandBus, EventBus, EventStore, Sagas) lives at broadway.github.io/broadway/ — not in this package's README. Start with these files in the vendor directory:
src/*ServiceProvider.php) — defines every binding, alias, and tag the package gives you.config/broadway.php) — check whether it is publishable: php artisan vendor:publish --provider="MicroModule\\Broadway\\...".Testing/ or Concerns/ directory, or a trait such as InteractsWithBroadway) — this is the package's main value proposition.The wrapper's job is to bind Broadway's interfaces into Laravel's container. Verify with:
php artisan tinker
>>> app(\Broadway\CommandHandling\CommandBus::class);
>>> app(\Broadway\EventHandling\EventBus::class);
>>> app(\Broadway\EventStore\EventStore::class);
If any of these fail to resolve, the service provider is not registered (check composer.json → extra.laravel.providers and your bootstrap/providers.php / config/app.php).
Do not start with your domain. Start with a single aggregate and prove the loop works: command → handler → aggregate → event → in-memory event store → read back.
use Broadway\CommandHandling\Testing\TraceableCommandBus;
use Broadway\EventStore\InMemoryEventStore;
public function test_account_can_be_opened(): void
{
$store = new InMemoryEventStore();
$repository = new SimpleEventSourcingRepository($store, new AccountAggregateFactory());
// dispatch OpenAccount command, assert AccountOpened event is in the store
}
Once that test is green, you know the package's wiring (serializers, buses, repository factories) matches what Broadway expects. Only then configure the persistent (DBAL/Doctrine) event store.
Broadway is not Laravel-native; it will not auto-discover handlers the way laravel/bus discovers jobs. The idiomatic pattern is a hand-maintained SimpleCommandBus registration, typically inside a service provider or a dedicated CommandBusFactory:
$commandBus = new SimpleCommandBus();
$commandBus->subscribe(new OpenAccountHandler($repository));
If the package provides a container-aware command bus or handler tag (broadway.command-handler or similar), prefer tagging:
$this->app->tag([OpenAccountHandler::class], 'broadway.command_handler');
and then resolve $this->app->tagged('broadway.command_handler') in the bus factory. Check the provider for the exact tag name — this package deliberately stays small, so the convention is defined there, not in docs.
For anything beyond a demo, create a BillingServiceProvider per module that:
SimpleEventSourcingRepository + factory).Keeping each context's wiring isolated means you can swap the event store (in-memory for a context test, DBAL for integration) without touching the app-wide singleton.
Broadway projectors are plain listeners. Map them in the same provider so the event bus and the read-model writes stay in sync:
$eventBus->subscribe(new OrderProjector($connection));
Because this package targets Laravel, the practical shortcut is to inject Illuminate\Database\Connection or an Eloquent model directly into projectors and drop Broadway's ReadModel abstraction — but do this consistently, not per-module, or you will end up with two parallel projection styles.
The testing helpers should let you drive the system without infrastructure. Typical shape:
use MicroModule\Broadway\Testing\InteractsWithBroadway;
class TransferMoneyTest extends TestCase
{
use InteractsWithBroadway;
public function test_money_is_transferred(): void
{
$this->given(new AccountOpened($id, 100));
$this->when(new TransferMoney($id, $to, 40));
$this->then(new MoneyTransferred($id, $to, 40));
}
}
This is the given/when/then event-sourcing test style from Broadway's Scenario. Use it for every aggregate. If the package's trait exposes assertEventsEmitted, assertCommandHandled, or a traceable command bus, wrap them in your own base TestCase so you can swap the implementation if the package is abandoned.
Controllers should dispatch commands, never touch repositories:
public function store(OpenAccountRequest $request, CommandBus $bus): Response
{
$bus->dispatch(new OpenAccount(Uuid::uuid4()->toString(), $request->input('name')));
return response()->noContent(201);
}
If the package does not provide a Laravel command-bus decorator that participates in transactions, dispatch inside DB::transaction(...) yourself — event-sourced aggregates and read-model writes must not diverge on a failed write.
0 stars, 0 dependents, last release July 2023. Treat this as vendored code: pin an exact version ("micro-module/broadway": "1.0.3" rather than ^1.0), and be prepared to fork. Before committing, read composer.json — check the PHP constraint against your runtime and whether broadway/broadway is a loose (^) dependency, which can drag in breaking changes.
If the package predates Laravel 11's bootstrap/providers.php and only registers via extra.laravel.providers, it still works, but package discovery can silently fail under --no-dev optimized autoloaders if composer dump-autoload -o wasn't run. Symptom: Target [CommandBus] is not instantiable. Fix: php artisan optimize:clear && composer dump-autoload.
InMemoryEventStore loses everything between requests. If a dev accidentally binds it in AppServiceProvider, tests pass and production silently forgets events. Guard against this: bind the in-memory store only inside TestCase::setUp() or a testing environment check, never in a shared provider.
Broadway serializes aggregates and events. When you add a field to an event class, old stored events still deserialize if you use SimpleSerializer, but renaming a class or property will break replay. Establish a rule from day one: event classes are append-only and immutable. Keep an upcaster if you must evolve them, and never "tidy up" event property names.
There is no artisan inspector in this package. When a projector misbehaves, drop to SQL against your event store table:
SELECT * FROM event_store WHERE uuid = ? ORDER BY playhead;
and manually json_decode the payload. Keeping the DBAL event store on a normal MySQL/Postgres connection (rather than an opaque stream store) is the single biggest debugging ergonomics win.
SimpleCommandBus throws on an unhandled command, but some Broadway versions log-and-ignore depending on the configured bus. Assert in every command test that some event was emitted, not just that no exception was thrown. Likewise, subscribers registered after the bus is resolved from the container won't be attached to the singleton — register in a provider, not in a controller.
InMemoryEventStore for Broadway's DBALEventStore (or your own implementing EventStore) — bind it in the provider; nothing else in the package should know which one is used.SimpleSerializer yourself and pass it to the repository factory.CommandBus and EventBus are interfaces — to add middleware (logging, transactions), write a small decorator implementing the same interface and bind that. This is safer than modifying the package.Because Broadway's interfaces are the real contract, you can drop the wrapper in an afternoon: register SimpleCommandBus/SimpleEventBus/your chosen event store directly in AppServiceProvider, and copy the testing trait into your tests/ directory. Design your handlers and projectors so they depend only on Broadway interfaces, never on MicroModule\Broadway\* classes, and the migration cost stays near zero.
How can I help you explore Laravel packages today?