Installation
composer require broadway/broadway-bundle
Symfony Flex auto-configures the bundle with in-memory implementations (for development only).
First Use Case: Domain Event Handling Define a simple event:
// src/Domain/Event/ExampleEvent.php
namespace App\Domain\Event;
use Broadway\Domain\DomainEvent;
class ExampleEvent extends DomainEvent
{
public function __construct(private string $message)
{
}
public function getMessage(): string
{
return $this->message;
}
}
Register Event Handlers
// src/Domain/EventListener/ExampleEventListener.php
namespace App\Domain\EventListener;
use App\Domain\Event\ExampleEvent;
use Broadway\EventHandling\EventListener;
class ExampleEventListener implements EventListener
{
public function handle(object $event): void
{
if (!$event instanceof ExampleEvent) {
return;
}
// Handle the event (e.g., log, update read model)
info('Event handled: ' . $event->getMessage());
}
}
Configure in config/packages/broadway.yaml
broadway:
event_handlers:
- App\Domain\EventListener\ExampleEventListener
Dispatch an Event
use Broadway\Domain\DomainMessage;
use Broadway\Domain\DomainRepository;
use Broadway\EventHandling\EventBus;
// In a service/controller
$eventBus = $container->get(EventBus::class);
$eventBus->dispatch(new ExampleEvent('Hello, Broadway!'));
Aggregate Root Management Define aggregates with invariants and domain events:
// src/Domain/Aggregate/ExampleAggregate.php
namespace App\Domain\Aggregate;
use App\Domain\Event\ExampleEvent;
use Broadway\Domain\AggregateRoot;
class ExampleAggregate extends AggregateRoot
{
public function doSomething(string $input): void
{
$this->recordThat(new ExampleEvent($input));
}
}
Repository Integration
Use DomainRepository to persist aggregates:
// src/Infrastructure/Persistence/ExampleAggregateRepository.php
namespace App\Infrastructure\Persistence;
use App\Domain\Aggregate\ExampleAggregate;
use Broadway\Domain\DomainRepository;
class ExampleAggregateRepository extends DomainRepository
{
protected function getFQCN(): string
{
return ExampleAggregate::class;
}
}
Event Sourcing Pattern Replay events to reconstruct state:
$repository = $container->get(ExampleAggregateRepository::class);
$aggregate = $repository->getAggregateRoot($aggregateId);
Read Model Projections
Use Projection to update read models:
// src/Infrastructure/Projection/ExampleProjection.php
namespace App\Infrastructure\Projection;
use App\Domain\Event\ExampleEvent;
use Broadway\ReadModel\Projection;
class ExampleProjection implements Projection
{
public function __invoke(object $event): void
{
if ($event instanceof ExampleEvent) {
// Update read model (e.g., Doctrine, Eloquent, or custom)
}
}
}
Command Handling Dispatch commands to trigger domain logic:
// src/Domain/Command/ExampleCommand.php
namespace App\Domain\Command;
use Broadway\CommandHandling\Command;
class ExampleCommand implements Command
{
public function __construct(private string $input) {}
public function getInput(): string { return $this->input; }
}
// Command handler
use Broadway\CommandHandling\CommandHandler;
class ExampleCommandHandler implements CommandHandler
{
public function handle(ExampleCommand $command): void
{
$aggregate = new ExampleAggregate();
$aggregate->doSomething($command->getInput());
$repository->save($aggregate);
}
}
In-Memory Event Store
broadway:
event_store:
projectors: [App\Infrastructure\Projection\ExampleProjection]
repository: broadway.event_store.doctrine
Event Listener Order
use Broadway\EventHandling\EventListenerPriority;
class HighPriorityListener implements EventListener, EventListenerPriority
{
public function getPriority(): int { return 10; }
// ...
}
Aggregate Identity
Transaction Boundaries
$entityManager->beginTransaction();
try {
$repository->save($aggregate);
$entityManager->commit();
} catch (\Exception $e) {
$entityManager->rollback();
throw $e;
}
Event Versioning
EventVersion to handle backward compatibility:
use Broadway\Domain\DomainEvent;
class VersionedEvent extends DomainEvent
{
public function getVersion(): int { return 1; }
}
Enable Debugging
Configure logging in config/packages/monolog.yaml:
monolog:
handlers:
broadway:
type: stream
path: "%kernel.logs_dir%/broadway.log"
level: debug
Check Event Bus Verify events are dispatched and handled:
$eventBus = $container->get(EventBus::class);
$eventBus->dispatch(new ExampleEvent('Test'));
// Check logs for confirmation
Projection Debugging
Use ProjectionManager to inspect projections:
$projectionManager = $container->get('broadway.read_model.projection_manager');
$projectionManager->getProjection(ExampleProjection::class)->__invoke(new ExampleEvent('Test'));
Custom Event Store
Implement EventStore interface for specialized storage (e.g., Kafka, RabbitMQ):
use Broadway\EventStore\EventStore;
class CustomEventStore implements EventStore
{
public function append(DomainMessage $domainMessage): void
{
// Custom logic
}
// ...
}
Middleware for Commands/Events Add middleware to commands or events:
use Broadway\CommandHandling\CommandMiddleware;
use Broadway\EventHandling\EventMiddleware;
class LoggingMiddleware implements CommandMiddleware, EventMiddleware
{
public function handle(object $message): void
{
info('Processing: ' . get_class($message));
}
}
Custom Metadata Attach metadata to events for auditing:
use Broadway\Domain\Metadata;
$metadata = new Metadata();
$metadata->set('user_id', 123);
$eventBus->dispatch(new ExampleEvent('Test', $metadata));
Symfony Messenger Bridge Integrate with Symfony Messenger for async processing:
broadway:
messenger:
enabled: true
transport: async
How can I help you explore Laravel packages today?