Implementing an event bus¶
The classes and interfaces from this package can also be used to set up an event bus. The characteristics of an event bus are:
- It handles events, i.e. informational messages
- Zero or more event subscribers will be notified of the occurance of an event
- The behavior of the event bus is extensible: middlewares are allowed to do things before or after handling an event
Setting up the event bus¶
At least we need an instance of MessageBusSupportingMiddleware
:
1 2 3 | use SimpleBus\Message\Bus\Middleware\MessageBusSupportingMiddleware;
$eventBus = new MessageBusSupportingMiddleware();
|
Finish handling an event, before handling the next¶
We want to make sure that events are always fully handled before other events will be handled, so we add a specialized middleware for that:
1 2 3 | use SimpleBus\Message\Bus\Middleware\FinishesHandlingMessageBeforeHandlingNext;
$eventBus->appendMiddleware(new FinishesHandlingMessageBeforeHandlingNext());
|
Defining the event subscriber collection¶
We want any number of event subscribers to be notified of a given event. We first need to define the collection of event subscribers that are available in the application. We should make this event subscriber collection lazy-loading, or every event subscriber will be fully loaded, even though it is not going to be used:
1 2 3 4 5 6 7 8 9 10 11 | use SimpleBus\Message\CallableResolver\CallableCollection;
use SimpleBus\Message\CallableResolver\ServiceLocatorAwareCallableResolver;
// Provide a map of event names to callables. You can provide actual callables, or lazy-loading ones.
$eventSubscribersByEventName = [
Fully\Qualified\Class\Name\Of\Event::class => [ // an array of "callables",
...,
...
]
...
];
|
Each of the provided “callables” can be one of the following things:
- An actual PHP callable,
- A service id (string) which the service locator (see below) can resolve to a PHP callable,
- An array of which the first value is a service id (string), which the service locator can resolve to a regular object, and the second value is a method name.
For backwards compatibility an object with a notify()
method also
counts as a “callable”.
1 2 3 4 5 6 7 8 9 10 11 | // Provide a service locator callable. It will be used to instantiate a subscriber service whenever requested.
$serviceLocator = function ($serviceId) {
$handler = ...;
return $handler;
};
$eventSubscriberCollection = new CallableCollection(
$eventSubscribersByEventName,
new ServiceLocatorAwareCallableResolver($serviceLocator)
);
|
Resolving the event subscribers for an event¶
The name of an event¶
First we need a way to resolve the name of an event. You can use the fully-qualified class name (FQCN) of an event object as its name:
1 2 3 | use SimpleBus\Message\Name\ClassBasedNameResolver;
$eventNameResolver = new ClassBasedNameResolver();
|
Or you can ask event objects what their name is:
1 2 3 | use SimpleBus\Message\Name\NamedMessageNameResolver;
$eventNameResolver = new NamedMessageNameResolver();
|
In that case your events have to implement NamedMessage
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | use SimpleBus\Message\Name\NamedMessage;
class YourEvent implements NamedMessage
{
public static function messageName()
{
return 'your_event';
}
}
.. rubric:: Implementing your own ``MessageNameResolver``
:name: implementing-your-own-messagenameresolver
If you want to use another rule to determine the name of an event,
create a class that implements
``SimpleBus\Message\Name\MessageNameResolver``.
|
Resolving the event subscribers based on the name of the event¶
Using the MessageNameResolver
of your choice, you can now let the
event subscribers resolver find the right event subscribers for a
given event.
1 2 3 4 5 6 | use SimpleBus\Message\Subscriber\Resolver\NameBasedMessageSubscriberResolver;
$eventSubscribersResolver = new NameBasedMessageSubscriberResolver(
$eventNameResolver,
$eventSubscriberCollection
);
|
Finally, we should add some middleware to the event bus that notifies all of the resolved event subscribers:
1 2 3 4 5 6 7 | use SimpleBus\Message\Subscriber\NotifiesMessageSubscribersMiddleware;
$eventBus->appendMiddleware(
new NotifiesMessageSubscribersMiddleware(
$eventSubscribersResolver
)
);
|
Using the event bus: an example¶
Consider the following event:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class UserRegistered
{
private $userId;
public function __construct(UserId $userId)
{
$this->userId = $userId;
}
public function userId()
{
return $this->userId;
}
}
|
This event conveys the information that “a new user was registered”. The message data consists of the unique identifier of the user that was registered. This information is required for event subscribers to act upon the event.
A subscriber for this event looks like this:
1 2 3 4 5 6 7 8 9 10 11 | class SendWelcomeMailWhenUserRegistered
{
...
public function notify($message)
{
$user = $this->userRepository->byId($message->userId());
// send the welcome mail
}
}
|
We should register this subscriber as a service and add the service id
to the event subscriber collection.
Since we have already fully configured the event bus, we can just start
creating a new event object and let the event bus handle it. Eventually
the event will be passed as a message to the
SendWelcomeMailWhenUserRegistered
event subscriber:
1 2 3 4 5 | $userId = $this->userRepository->nextIdentity();
$event = new UserRegistered($userId);
$eventBus->handle($event);
|
Implementing your own event bus middleware¶
It’s very easy to extend the behavior of the event bus. You can
create a class that implements MessageBusMiddleware
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | use SimpleBus\Message\Bus\Middleware\MessageBusMiddleware;
/**
* Marker interface for domain events that should be stored in the event store
*/
interface DomainEvent
{
}
class StoreDomainEvents implements MessageBusMiddleware
{
// ...
public function handle($message, callable $next)
{
if ($message instanceof DomainEvent) {
// store the domain event
$this->eventStore->add($message);
}
// let other middlewares do their job
$next($message);
}
}
|
You should add an instance of that class as middleware to any
MessageBusSupportingMiddleware
instance (like the event bus we
created earlier):
1 | $eventBus->appendMiddleware(new StoreDomainEvents());
|
Make sure that you do this at the right place, before or after you add the other middlewares.
Calling $next($message)
will make sure that the next middleware
in line is able to handle the message.
Logging messages¶
To log every message that passes through the event bus, add the
LoggingMiddleware
right before the
NotifiesMessageSubscribersMiddleware
. Make sure to set up a
PSR-3 compliant logger
first:
1 2 3 4 5 6 7 8 | use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use SimpleBus\Message\Logging\LoggingMiddleware;
// $logger is an instance of LoggerInterface
$logger = ...;
$loggingMiddleware = new LoggingMiddleware($logger, LogLevel::DEBUG);
$eventBus->appendMiddleware($loggingMiddleware);
|
Continue to read about recording events and handling them.