Installation:
composer require beberlei/lite-cqrs:1.1
Use the 1.1 branch as dev-master is in active refactoring.
First Command:
// Define a command (no interface needed)
class CreateUserCommand {
public $name;
public $email;
}
// Define a handler
class UserCommandHandler {
public function createUser(CreateUserCommand $command) {
// Business logic here
return new UserCreatedEvent($command->name, $command->email);
}
}
// Register and dispatch
$commandBus = new \LiteCQRS\Bus\DirectCommandBus();
$commandBus->register('CreateUserCommand', new UserCommandHandler());
$command = new CreateUserCommand();
$command->name = 'John Doe';
$command->email = 'john@example.com';
$commandBus->dispatch($command);
First Event:
// Define an event
class UserCreatedEvent {
public $name;
public $email;
}
// Define an event handler
class UserEventHandler {
public function onUserCreated(UserCreatedEvent $event) {
// Side effects (e.g., notifications, analytics)
}
}
// Register the event handler
$eventBus = new \LiteCQRS\Bus\InMemoryEventMessageBus();
$eventBus->register(new UserEventHandler());
CreateUserCommand → createUser() method).example1.php for a full workflow.LiteCQRSBundle.Command Creation: Use DTO-like classes with public properties (no getters/setters needed). Example:
class UpdateUserEmailCommand {
public $userId;
public $newEmail;
}
Handler Registration:
Register handlers via the CommandBus using the class shortname as the key.
$commandBus->register('UpdateUserEmailCommand', $userService);
Event Emission:
Emit events from handlers using AggregateRoot::apply() (for event sourcing) or DomainEventProvider::raise() (for simple pub/sub).
class UserService {
public function updateEmail(UpdateUserEmailCommand $command) {
$user = $this->userRepository->find($command->userId);
$user->apply(new UserEmailUpdatedEvent($command->newEmail));
}
}
Event Handling:
Implement onEventName() methods in event handlers.
class EmailNotificationHandler {
public function onUserEmailUpdated(UserEmailUpdatedEvent $event) {
Mail::send('emails.user_updated', ['email' => $event->newEmail]);
}
}
Dependency Injection: Use Laravel’s service container to bind commands/handlers:
$this->app->bind('UpdateUserEmailCommand', function () {
return new UpdateUserEmailCommand();
});
Middleware:
Wrap the CommandBus in middleware for logging, validation, or transactions:
class CommandLoggingMiddleware {
public function handle($command, Closure $next) {
Log::info("Dispatching: " . get_class($command));
return $next($command);
}
}
Event Sourcing:
For aggregates, implement AggregateRoot and apply() methods:
class User implements AggregateRoot {
public function apply(UserEmailUpdatedEvent $event) {
$this->email = $event->newEmail;
}
}
Sequential Commands:
Use SequentialCommandBus to ensure commands execute in order:
$commandBus = new \LiteCQRS\Bus\SequentialCommandBus();
Naming Conventions:
CreateUserCommand → createUser()).
Fix: Use IDE refactoring to rename classes/methods in sync.on (e.g., onUserCreated()).
Fix: Rename methods if the convention is violated.Event Sourcing:
apply() for events breaks replayability.
Fix: Use AggregateRoot trait and define applyEventName() methods.Circular Dependencies:
CommandBus with a queue (e.g., InMemoryCommandBus) to track in-flight commands.Transaction Boundaries:
EventMessageHandlerFactory with a transactional CommandBus.Symfony Autowiring:
LiteCQRSBundle requires explicit tags (lite_cqrs.command_handler).
Fix: Add tags to service definitions:
services:
App\CommandHandlers\UserCommandHandler:
tags: ['lite_cqrs.command_handler']
Failed Events:
Listen for EventExecutionFailed to catch handler errors:
$eventBus->register(new class {
public function onEventExecutionFailed(EventExecutionFailed $event) {
Log::error("Event failed: " . $event->exception->getMessage());
}
});
Command Not Found:
Ensure the handler method name matches the command’s short class name.
Debug: Use DirectCommandBus::getHandler() to inspect registrations.
Event Not Triggered:
Verify the event class shortname matches the handler method (e.g., UserCreatedEvent → onUserCreated()).
Custom CommandBus:
Extend \LiteCQRS\Bus\CommandBus to add pre/post-processing:
class LoggingCommandBus extends \LiteCQRS\Bus\CommandBus {
public function dispatch($command) {
Log::info("Dispatching: " . get_class($command));
parent::dispatch($command);
}
}
Custom EventQueue:
Implement \LiteCQRS\Bus\EventQueue for async processing (e.g., Redis):
class RedisEventQueue implements EventQueue {
public function getEvents() { /* ... */ }
public function clear() { /* ... */ }
}
Monolog Integration:
Enable logging via the MonologPlugin:
$monologPlugin = new \LiteCQRS\Plugin\MonologPlugin($logger);
$commandBus->addHandler($monologPlugin);
Service Providers:
Register the CommandBus and EventBus in a provider:
public function register() {
$this->app->singleton(\LiteCQRS\Bus\CommandBus::class, function () {
$bus = new \LiteCQRS\Bus\DirectCommandBus();
$bus->register('CreateUserCommand', $this->app->make(UserCommandHandler::class));
return $bus;
});
}
Artisan Commands:
Use the CommandBus to trigger commands from CLI:
class SendWelcomeEmailCommand extends Command {
protected $signature = 'cqrs:send-welcome {userId}';
public function handle() {
$commandBus = app(\LiteCQRS\Bus\CommandBus::class);
$commandBus->dispatch(new SendWelcomeEmailCommand($this->argument('userId')));
}
}
Testing:
Mock the CommandBus and EventBus in tests:
$mockBus = Mockery::mock(\LiteCQRS\Bus\CommandBus::class);
$mockBus->shouldReceive('dispatch')->once();
$this->app->instance(\LiteCQRS\Bus\CommandBus::class, $mockBus);
How can I help you explore Laravel packages today?