Skip to content

Commit

Permalink
Merge pull request #45 from codeliner/message-bus
Browse files Browse the repository at this point in the history
Message bus
  • Loading branch information
codeliner committed Jan 13, 2015
2 parents a97c4ab + 7190def commit e920a05
Show file tree
Hide file tree
Showing 13 changed files with 404 additions and 501 deletions.
19 changes: 14 additions & 5 deletions docs/command_bus.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ external system in your test environment with a connection to a mocked command h
# API

```php
class CommandBus implements \Zend\EventManager\EventManagerAwareInterface
class CommandBus extends MessageBus
{
/**
* @param \Zend\EventManager\ListenerAggregateInterface|\Zend\Log\LoggerInterface $plugin
Expand Down Expand Up @@ -59,6 +59,8 @@ class CommandBus implements \Zend\EventManager\EventManagerAwareInterface
The public api of the CommandBus is very simple. Four of the five methods deal with adding or removing plugins and the last
one triggers the dispatch process of the given command.

** Note: Only `dispatch` is implemented by the CommandBus the four other public methods are provided by the basic MessageBus implementation.

# Event-Driven Dispatch

The command dispatch is an event-driven process provided by [Zend\EventManager](http://framework.zend.com/manual/2.0/en/modules/zend.event-manager.event-manager.html).
Expand All @@ -69,20 +71,27 @@ it does not know which command handler is responsible for the command.
Following events are triggered in the listed order:

- `initialize`: This event is triggered right after CommandBus::dispatch($command) is invoked. At this time the CommandDispatch only contains the command.

- `detect-message-name` (optional): Before a command handler can be located, the CommandBus needs to know how the command is named. Their are two
possibilities to provide the information. The command can implement the [Prooph\ServiceBus\Message\MessageNameProvider](../src/Prooph/ServiceBus/Message/MessageNameProvider.php) interface.
In this case the CommandBus picks the command name directly from the command and inject it manually in the CommandDispatch. The `detect-message-name` event is not triggered. If the command
does not implement the interface the `detect-message-name` event is triggered and a plugin needs to inject the name using `CommandDispatch::setCommandName`.

- `route`: During the `route` event a plugin should provide the responsible command handler either in form of a ready to use object or callable or as a string
representing an alias of the command handler that can be used by a DIC to locate an instance. The plugin should provide the handler by using
`CommandDispatch::setCommandHandler`.

- `locate-handler` (optional): After routing the command, the CommandBus checks if the command handler was provided as a string. If so it triggers the
`locate-handler` event. This is the latest time to provide an object or callable as command handler. If no plugin was able to provide one the CommandBus throws an exception.

- `invoke-handler`: Having the command handler in place it's time to invoke it with the command. The CommandBus always triggers the event. It performs no default action even if the
command handler is a callable.

- `handle-error`: If at any time a plugin or the CommandBus itself throws an exception it is caught and passed to the CommandDispatch. The normal event chain breaks and a
`handle-error` event is triggered instead. Listeners can access the exception by calling `CommandDispatch::getException`. When all listeners are informed about the error
the CommandBus throws a Prooph\ServiceBus\Exception\CommandDispatchException to inform the outside world about the error.
`handle-error` event is triggered instead. Listeners can access the exception by calling `CommandDispatch::getException`.
A `handle-error` listener or a `finalize` listener can unset the exception by calling `CommandDispatch::setException(null)`.
When all listeners are informed about the error and no one has unset the exception the CommandBus throws a Prooph\ServiceBus\Exception\CommandDispatchException to inform the outside world about the error.

- `finalize`: This event is always triggered at the end of the process no matter if the process was successful or an exception was thrown. It is the ideal place to
attach a monitoring plugin.

Expand All @@ -97,8 +106,8 @@ your command handlers with the command. Mix and match the plugins provided by PS
# Plugins

Plugins can be simple callables (use the methods `on` and `off` to attach/detach them), implementations of the
Zend\EventManager\ListenerAggregateInterface (use the methods `ùtilize` and `deactivate` to attach/detach them) or an instance of
Zend\Log\LoggerInterface (also use methods `ùtilize` and `deactivate` to attach/detach it).
Zend\EventManager\ListenerAggregateInterface (use the methods `utilize` and `deactivate` to attach/detach them) or an instance of
Zend\Log\LoggerInterface (also use methods `utilize` and `deactivate` to attach/detach it).
The signature of a plugin method/callable that listens on a CommandDispatch event is:

```php
Expand Down
18 changes: 14 additions & 4 deletions docs/event_bus.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ class EventBus implements \Zend\EventManager\EventManagerAwareInterface
The public api of the EventBus is very simple. Four of the five methods deal with adding or removing plugins and the last
one triggers the dispatch process of the given event.

** Note ** For the event-driven dispatch process the term `event` is used, too. For example the first argument of the
** Note: Only `dispatch` is implemented by the EventBus the four other public methods are provided by the basic MessageBus implementation.

** Note: For the event-driven dispatch process the term `event` is used, too. For example the first argument of the
method `on` is called "eventName" or plugins should implement the Zend\EventManager\ListenerAggregateInterface. But these
namings have nothing to do with the event messages dispatched by the EventBus. The same wording is used to describe something that happens
now (the dispatch process) and something that happened in the past (the event message).
Expand All @@ -70,22 +72,30 @@ it does not know which event message listener is interested in the event message
Following process events are triggered in the listed order:

- `initialize`: This process event is triggered right after EventBus::dispatch($event) is invoked. At this time the EventDispatch only contains the event message.

- `detect-message-name` (optional): Before an event message listener can be located, the EventBus needs to know how the event message is named. Their are two
possibilities to provide the information. The event message can implement the [Prooph\ServiceBus\Message\MessageNameProvider](../src/Prooph/ServiceBus/Message/MessageNameProvider.php) interface.
In this case the EventBus picks the message name directly from the event message and inject it manually in the EventDispatch. The `detect-message-name` event is not triggered. If the event message
does not implement the interface the `detect-message-name` process event is triggered and a plugin needs to inject the name using `EventDispatch::setEventName`.

- `route`: During the `route` event one or more plugins should provide a list of interested event message listeners either in form of ready to use objects or callables or as strings
representing aliases of the event message listeners that can be used by a DIC to locate the listener instances. The plugins should provide and modify the list by using
`EventDispatch::setEventListeners` and `EventDispatch::addEventListener`.

- `locate-listener` (optional): After routing the event message, the EventBus loops over the list of interested event message listeners and checks for each of them
if the event message listener was provided as a string. If so it triggers a
`locate-listener` process event. This is the latest time to provide an object or callable as event message listener. The listener alias can be requested from the EventDispatch by
calling the method `EventDispatch::getCurrentEventListener` and the event message listener instance can be set via method `EventDispatch::setCurrentEventListener` If no plugin was able to provide an instance the EventBus throws an exception.

- `invoke-listener`: Within the listener list loop the EventBus triggers the `invoke-listener` process event. The EventBus always triggers the event. It performs no default action even if the
event message listener is a callable. Plugins can access the currently active listener from the list by requesting it from the `EventDispatch::getCurrentEventListener` method.

- `handle-error`: If at any time a plugin or the EventBus itself throws an exception it is caught and passed to the EventDispatch. The normal process event chain breaks and a
`handle-error` event is triggered instead. Plugins can access the exception by calling `EventDispatch::getException`. When all interested plugins are informed about the error
`handle-error` event is triggered instead. Plugins can access the exception by calling `EventDispatch::getException`.
A `handle-error` listener or a `finalize` listener can unset the exception by calling `CommandDispatch::setException(null)`.
When all listeners are informed about the error and no one has unset the exception
the EventBus throws a Prooph\ServiceBus\Exception\EventDispatchException to inform the outside world about the error.

- `finalize`: This process event is always triggered at the end of the process no matter if the process was successful or an exception was thrown. It is the ideal place to
attach a monitoring plugin.

Expand All @@ -100,8 +110,8 @@ your event message listeners with the event message. Mix and match the plugins p
# Plugins

Plugins can be simple callables (use the methods `on` and `off` to attach/detach them), implementations of the
Zend\EventManager\ListenerAggregateInterface (use the methods `ùtilize` and `deactivate` to attach/detach them) or an instance of
Zend\Log\LoggerInterface (also use methods `ùtilize` and `deactivate` to attach/detach it).
Zend\EventManager\ListenerAggregateInterface (use the methods `utilize` and `deactivate` to attach/detach them) or an instance of
Zend\Log\LoggerInterface (also use methods `utilize` and `deactivate` to attach/detach it).
The signature of a plugin method/callable that listens on an EventDispatch is:

```php
Expand Down
3 changes: 2 additions & 1 deletion src/Prooph/ServiceBus/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
/**
* Class Command
*
* Can be used as base class for commands.
* The class can be used as base class for commands, but it is no requirement.
* You can dispatch all kinds of messages as long as you register plugins that are able to handle your messages.
*
* @package Prooph\ServiceBus\Command
* @author Alexander Miertsch <[email protected]>
Expand Down
135 changes: 2 additions & 133 deletions src/Prooph/ServiceBus/CommandBus.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,97 +14,16 @@
use Prooph\ServiceBus\Exception\CommandDispatchException;
use Prooph\ServiceBus\Exception\RuntimeException;
use Prooph\ServiceBus\Process\CommandDispatch;
use Zend\EventManager\EventManager;
use Zend\EventManager\EventManagerAwareInterface;
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
use Zend\Log\LoggerInterface;
use Zend\Stdlib\CallbackHandler;

/**
* Class CommandBus
*
* @package Prooph\ServiceBus
* @author Alexander Miertsch <[email protected]>
*/
class CommandBus implements EventManagerAwareInterface
class CommandBus extends MessageBus
{
/**
* @var LoggerInterface
*/
protected $logger;

/**
* @var EventManager
*/
protected $events;

/**
* @param ListenerAggregateInterface|LoggerInterface $plugin
* @return $this
* @throws Exception\RuntimeException
*/
public function utilize($plugin)
{
if ($plugin instanceof ListenerAggregateInterface) {
$plugin->attach($this->getEventManager());
} else if ($plugin instanceof LoggerInterface) {
$this->logger = $plugin;
} else {
throw new RuntimeException(
sprintf(
"CommandBus cannot use plugin of type %s.",
(is_object($plugin))? get_class($plugin) : gettype($plugin)
)
);
}

return $this;
}

/**
* @param ListenerAggregateInterface|LoggerInterface $plugin
* @return $this
* @throws Exception\RuntimeException
*/
public function deactivate($plugin)
{
if ($plugin instanceof ListenerAggregateInterface) {
$plugin->detach($this->getEventManager());
} else if ($plugin instanceof LoggerInterface) {
$this->logger = null;
} else {
throw new RuntimeException(
sprintf(
"CommandBus cannot detach plugin of type %s.",
(is_object($plugin))? get_class($plugin) : gettype($plugin)
)
);
}

return $this;
}

/**
* @param string $eventName
* @param callable $listener
* @param int $priority
* @return \Zend\Stdlib\CallbackHandler
*/
public function on($eventName, $listener, $priority = 1)
{
return $this->getEventManager()->attach($eventName, $listener, $priority);
}

/**
* @param CallbackHandler $callbackHandler
* @return bool
*/
public function off(CallbackHandler $callbackHandler)
{
return $this->getEventManager()->detach($callbackHandler);
}

/**
* @param mixed $command
* @throws Exception\CommandDispatchException
Expand Down Expand Up @@ -168,40 +87,6 @@ public function dispatch($command)
$this->triggerFinalize($commandDispatch);
}

/**
* @param CommandDispatch $commandDispatch
* @throws Exception\RuntimeException
*/
protected function trigger(CommandDispatch $commandDispatch)
{
$result = $this->getEventManager()->trigger($commandDispatch);

if ($result->stopped()) {
throw new RuntimeException("Dispatch has stopped unexpectedly.");
}
}

/**
* @param CommandDispatch $commandDispatch
*/
protected function triggerError(CommandDispatch $commandDispatch)
{
$commandDispatch->setName(CommandDispatch::HANDLE_ERROR);

$this->getEventManager()->trigger($commandDispatch);
}

/**
* @param CommandDispatch $commandDispatch
*/
protected function triggerFinalize(CommandDispatch $commandDispatch)
{
$commandDispatch->setName(CommandDispatch::FINALIZE);

$this->getEventManager()->trigger($commandDispatch);
}


/**
* Inject an EventManager instance
*
Expand All @@ -215,23 +100,7 @@ public function setEventManager(EventManagerInterface $eventManager)
__CLASS__
));

$this->events = $eventManager;
}

/**
* Retrieve the event manager
*
* Lazy-loads an EventManager instance if none registered.
*
* @return EventManagerInterface
*/
public function getEventManager()
{
if (is_null($this->events)) {
$this->setEventManager(new EventManager());
}

return $this->events;
parent::setEventManager($eventManager);
}
}

3 changes: 2 additions & 1 deletion src/Prooph/ServiceBus/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
/**
* Class Event
*
* Can be used as base event class
* The class can be used as base class for events, but it is no requirement.
* You can dispatch all kinds of messages as long as you register plugins that are able to handle your messages.
*
* @package Prooph\ServiceBus\Event
* @author Alexander Miertsch <[email protected]>
Expand Down
Loading

0 comments on commit e920a05

Please sign in to comment.