Skip to content

Commit

Permalink
Merge pull request #101 from prooph/develop
Browse files Browse the repository at this point in the history
v5.0
  • Loading branch information
codeliner committed Nov 22, 2015
2 parents f3b3c91 + 3309821 commit 5b617c2
Show file tree
Hide file tree
Showing 34 changed files with 690 additions and 358 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ nbproject
.project
.settings
vendor
composer.lock
composer.lock
docs/html
4 changes: 0 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ php:
- 5.6
- 7

matrix:
allow_failures:
- php: 7

before_script:
- composer self-update
- composer --dev install
Expand Down
60 changes: 26 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,36 @@
PSB - ProophServiceBus
======================
# PSB - ProophServiceBus

PHP 5.5+ lightweight message bus supporting CQRS and Micro Services

[![Build Status](https://travis-ci.org/prooph/service-bus.png?branch=master)](https://travis-ci.org/prooph/service-bus)
[![Coverage Status](https://coveralls.io/repos/prooph/service-bus/badge.svg?branch=master&service=github)](https://coveralls.io/github/prooph/service-bus?branch=master)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/prooph/improoph)

Messaging API
-------------
## Messaging API

prooph/service-bus is a lightweight messaging facade.
It allows you to define the API of your model with the help of messages.

1. Command messages describe the actions your model can handle.
2. Event messages describe things that happened while your model handled a command.
3. Query messages describe available information that can be fetched from your model.
1. **Command** messages describe actions your model can handle.
2. **Event** messages describe things that happened while your model handled a command.
3. **Query** messages describe available information that can be fetched from your (read) model.

prooph/service-bus shields your model. Data input and output ports become irrelevant and no longer influence the business logic.
I'm looking at you Hexagonal Architecture.
prooph/service-bus shields your model. Data input and output ports become irrelevant and no longer influence business logic.
We're looking at you Hexagonal Architecture.

prooph/service-bus decouples your model from any framework. You can use a
web framework like Zend, Symfony, Laravel and co. to handle http requests and pass them via prooph/service-bus to your model
but you can also receive the same messages via CLI or from a message queue system like RabbitMQ or Beanstalkd.
but you can also receive the same messages via CLI or from a messaging system like RabbitMQ or Beanstalkd.

![prooph_architecture](https://raw.githubusercontent.com/prooph/proophessor/master/docs/book/img/prooph_overview.png)
It is also a perfect fit for microservices architecture as it provides an abstraction layer for message-based inter-service communication.

![prooph_architecture](https://raw.githubusercontent.com/prooph/proophessor/master/docs/book/img/prooph_overview.png)

Installation
------------
## Installation

You can install prooph/service-bus via composer by adding `"prooph/service-bus": "~4.0"` as requirement to your composer.json.
You can install prooph/service-bus via composer by adding `"prooph/service-bus": "~5.0"` as requirement to your composer.json.

Quick Start
-----------
## Quick Start

```php
<?php
Expand Down Expand Up @@ -64,33 +61,28 @@ $commandBus->dispatch($echoText);
//Output should be: It works
```

Documentation
-------------
## Documentation

- [Overview](docs/service_bus_system.md)
- [Message Bus API](docs/message_bus.md)
- [Plugins](docs/plugins.md)
- [Async Message Producers](docs/async_message_producer.md)
- [Framework Integration](docs/factories.md)
Documentation is [in the docs tree](docs/), and can be compiled using [bookdown](http://bookdown.io).

```console
$ php ./vendor/bin/bookdown docs/bookdown.json
$ php -S 0.0.0.0:8080 -t docs/html/
```

Support
-------
Then browse to [http://localhost:8080/](http://localhost:8080/)

## Support

- Ask questions on [prooph-users](https://groups.google.com/forum/?hl=de#!forum/prooph) google group.
- File issues at [https://github.com/prooph/service-bus/issues](https://github.com/prooph/service-bus/issues).
- Say hello in the [prooph gitter](https://gitter.im/prooph/improoph) chat.

Contribute
----------
## Contribute

Please feel free to fork and extend existing or add new features and send a pull request with your changes!
To establish a consistent code quality, please provide unit tests for all your changes and may adapt the documentation.

# Dependencies

Please refer to the project [composer.json](composer.json) for the list of dependencies.

License
-------
## License

Released under the [New BSD License](LICENSE).
Released under the New BSD License.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"fabpot/php-cs-fixer": "1.7.*",
"satooshi/php-coveralls": "dev-master",
"container-interop/container-interop" : "^1.1",
"sandrokeil/interop-config": "^0.3"
"sandrokeil/interop-config": "^0.3",
"tobiju/bookdown-bootswatch-templates": "^0.2.0"
},
"suggest": {
"prooph/event-store": "Let the EventBus dispatch persisted DomainEvents",
Expand Down
99 changes: 61 additions & 38 deletions docs/async_message_producer.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,61 @@
Async Message Producer
======================

[Back to documentation](../README.md#documentation)

# Async Messaging

Messaging becomes really interesting when you process your messages asynchronous. For example push your messages on a database queue,
set up a cron job to periodically check the queue for new messages and process them. The bus implementations of PSB can
hide such an asynchronous workflow behind a unified interface. You can start with synchronous message dispatching by
routing your messages directly to message handlers and if you later want to improve response times you can switch to
async processing on a per message basis by routing the appropriate messages to a [Async\MessageProducer](../src/Async/MessageProducer.php) listed below.

# Available MessageProducer

- [BernardProducer](https://github.com/prooph/psb-bernard-producer): Queue multi-backend providing different
drivers like Doctrine DBAL and Predis (see http://bernardphp.com for a complete list of drivers)
- [GuzzleHttpProducer](https://github.com/prooph/psb-http-producer): Send messages to a remote system using
HTTP
- [ZeromqProducer](https://github.com/prooph/psb-zeromq-producer): Async message handling using super fast and simple to
set up ZeroMQ

# Usage

If you want to set up a bus that handles all messages async you can do so by attaching a [MessageProducerPlugin](plugins.md#messageproducerplugin)
initialized with your message producer of choice to a message bus.

If you want to decide on a per message basis if the message should be handled async you can use a normal [message router](plugins.md#routers)
and configure your message producer of choice as message handler for the appropriate messages.

*Note: The [RegexRouter](plugins.md#proophservicebuspluginrouterregexrouter) is a good choice if you want to handle all messages of a specific namespace async.*

# QueryBus

A async message producer for the QueryBus needs to provide a response by resolving the handed over deferred.
In a messaging system based on ZeroMQ for example you can make use of request/response mode.
HTTP APIs provide responses naturally.
So these are both good candidates to use for remote querying.
# Async Message Producer

Messaging becomes really interesting if you process your messages asynchronous. PSB can
hide such an asynchronous workflow behind a unified interface. You can start with synchronous message dispatching by
routing your messages directly to message handlers and if you later want to improve response times you can switch to
async processing on a per message basis by routing the appropriate messages to a message producer which hands them over to
a messaging system like rabbitMQ, zeroMQ, gearman or beanstalkd.

## Available MessageProducer

- [BernardProducer](https://github.com/prooph/psb-bernard-producer): Queue multi-backend providing different
drivers like Doctrine DBAL and Predis (see [https://github.com/bernardphp/bernard](https://github.com/bernardphp/bernard) for a complete list of drivers)
- [GuzzleHttpProducer](https://github.com/prooph/psb-http-producer): Send messages to a remote system using
HTTP
- [ZeromqProducer](https://github.com/prooph/psb-zeromq-producer): Async message handling using super fast and simple to
set up ZeroMQ

## Usage

If you want to set up a bus that handles all messages async you can do so by attaching a `Prooph\ServiceBus\Plugin\MessageProducerPlugin`
initialized with your message producer of choice to a message bus.

Let's look at a simple example using the `psb-zeromq-producer`

```php
//app bootstrap
$container = new Container;
$container['config'] = [
'prooph' => [
'zeromq_producer' => [
'dsn' => 'tcp://127.0.0.1:5555', // ZMQ Server Address.
'persistent_id' => 'example', // ZMQ Persistent ID to keep connections alive between requests.
'rpc' => false, // Use as Query Bus.
]
]
];

$factory = \Prooph\ServiceBus\Message\ZeroMQ\Container\ZeroMQMessageProducerFactory;
$zmqProducer = $factory($container);

$commandBus = new \Prooph\ServiceBus\CommandBus();

$messageProducerForwarder = new \Prooph\ServiceBus\Plugin\MessageProducerPlugin($zmqProducer);

$commandBus->utilize($messageProducerForwarder);

$echoText = new ExampleCommand('It works');
$commandBus->dispatch($echoText);
```

You can also route individual messages to message producer by using a message router plugin.

*Note: `Prooph\ServiceBus\Plugin\Router\RegexRouter` is a good choice
if you want to handle all messages of a specific namespace async.*

## Async Querying

An async message producer for the QueryBus needs to provide a response by resolving the handed over `React\Promise\Deferred`.
When using a messaging system like ZeroMQ for example you can make use of request/response mode or RPC mode.
HTTP APIs provide responses naturally.
So these are both good candidates to use for remote querying.
13 changes: 13 additions & 0 deletions docs/bookdown.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"title": "PHP Enterprise Service Bus Implementation supporting CQRS and DDD",
"content": [
{"intro": "../README.md"},
{"overview": "service_bus_system.md"},
{"message_bus": "message_bus.md"},
{"plugins": "plugins.md"},
{"async_message_producer": "async_message_producer.md"},
{"factories": "factories.md"}
],
"target": "./html",
"template": "../vendor/tobiju/bookdown-bootswatch-templates/templates/main.php"
}
13 changes: 5 additions & 8 deletions docs/factories.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Interop + Factories

[Back to documentation](../README.md#documentation)

Instead of providing a module, a bundle, a bridge or similar framework integration prooph/service-bus ships with
`interop factories`.

Expand All @@ -21,20 +19,19 @@ always bootstrap a message bus by hand. Just look at the factories for inspirati

## Customizing via Configuration

In the `config` folder you will find a [package configuration skeleton](../config/prooph_service_bus.config.php)
and a [services configuration skeleton](../config/services.config.php) which contain the factories for the message guards.
The configuration is a simple PHP array flavored with some comments to help you understand the structure.
In the `config` folder of prooph/service-bus you will find example configuration files.
Configuration is a simple PHP array flavored with some comments to help you understand the structure.

Now follow the simple steps below to integrate prooph/service-bus in your framework and/or application.

1. Merge the configuration skeleton into your application config either by hand or by using the mechanism of your framework.
1. Merge configuration into your application config either by hand or by using the mechanism of your framework.
2. Customize the configuration so that it meet your needs. The comments in the config file will tell you more.
3. (Only required if not done by your framework) Make your application config available as a service in the
Inversion of Control container. Use `config` as the service id (common id for application config).
4. Register the message buses as services in your IoC container and use the [factories](../src/Container) to create the [message buses](../src).
4. Register the message buses as services in your IoC container and use the located in `src/Container` to create the different message buses.
How you can register a message bus depends on your container. Some containers like [zend-servicemanager](https://github.com/zendframework/zend-servicemanager)
or [pimple-interop](https://github.com/moufmouf/pimple-interop) allow you to map a service id to an `invokable factory`.
If you use such an IoC container you are lucky. In this case you can use the prooph/service-bus factories as-is.
We recommend using `Prooph\ServiceBus\<CommandBus/EventBus/QueryBus::class` as service id.

*Note: If you're still unsure how to do it you might have a look at the [BusFactoriesTest](../tests/Container/BusFactoriesTest.php)*.
*Note: If you're still unsure how to do it you might have a look at the `BusFactoriesTest` located in the `tests` folder.
34 changes: 18 additions & 16 deletions docs/message_bus.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
# Message Buses

[Back to documentation](../README.md#documentation)

## Commanding

When you want to apply [CQRS](http://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf) the command bus is your best friend.
It takes an incoming command message and route it to the responsible command handler.
It takes an incoming command message and routes it to the responsible command handler.
The advantage of using a CommandBus instead of calling command handlers directly is, that you can change your model without effecting
the application logic. You can work with command versions to dispatch a newer version to a new command handler and older
versions to old command handlers. Your model can support different versions at the same time which makes migrations a lot easier.
Expand All @@ -17,8 +15,7 @@ And for distributed systems it is also interesting to push the command on a queu
When dividing your domain logic into modules or bounded contexts you need a way to inform the outside world
about events that happened in your model.
An EventBus is responsible for dispatching event messages to all interested listeners. If a listener is part of another system
the event may need to be send to a remote interface. The Prooph\ServiceBus\EventBus is capable to handle synchronous event
dispatching as well as asynchronous/remote event dispatching by using suitable plugins.
the event may need to be send to a remote interface.

## Querying

Expand All @@ -27,11 +24,11 @@ The two most used protocols are HTTP request-response with resource API's and li
out-of-the-box but HTTP API's can be integrated too.
The QueryBus is responsible for routing a query message to a so called finder. The query indicates that the producer expects a response.
The finder's responsibility is to fetch data from a data source using the query parameters defined in the query message. It is up to the finder if the data is fetched synchronous
or asynchronous, so the QueryBus returns a [promise](https://github.com/reactphp/promise) to the query producer which gets resolved by the finder.
or asynchronous, so the QueryBus returns a `React\Promise\Promise` to the callee.

## API

All three bus types extend the same base class [Prooph\ServiceBus\MessageBus](../MessageBus.php) and therefor make use of an event-driven message dispatch process.
All three bus types extend the same base class `Prooph\ServiceBus\MessageBus` and therefor make use of an event-driven message dispatch process.
Take a look at the CommandBus API. It is the same for EventBus and QueryBus except that the QueryBus returns a promise from `QueryBus::dispatch`.

```php
Expand Down Expand Up @@ -74,20 +71,23 @@ But first let's take a look at the internals of a message dispatch process and t

### initialize

This action event is triggered right after MessageBus::dispatch($message) is invoked. At this time the action event only contains the `message`.
This action event is triggered right after `MessageBus::dispatch($message)` is invoked. At this time the action event only contains the `message`.

### detect-message-name (optional)

Before a message handler can be located, the message bus needs to know how the message is named. Their are two
possibilities to provide the information. The message can implement the [Prooph\Common\Messaging\HasMessageName](https://github.com/prooph/common/blob/master/src/Messaging/HasMessageName.php) interface.
In this case the message bus picks the name directly from the message and set it as param `message-name` in the action event for later use. The `detect-message-name` event is not triggered. If the message
does not implement the interface the `detect-message-name` event is triggered and a plugin needs to inject the name using `ActionEvent::setParam('message-name', $messageName)`.
Finally if no `message-name` was set by a listener the message bus uses the FQCN of message if it is an object or the type of message in all other cases.
If no `message-name` was set by a listener the message bus uses a fallback:
- FQCN of message in case of object
- message => message-name in case of string
- `gettype($message)` in all other cases

### route

During the `route` action event a plugin (typically a [router](plugins.md#routers)) should provide the responsible message handler either in form of a ready to use `callable`, a object or just a string.
The latter should be a service id that can be passed to a service locator to get an instance of the handler.
During the `route` action event a plugin (typically a router) should provide the responsible message handler either in form of a ready to use `callable`, an object or just a string.
The latter should be a service id that can be passed to a service locator to get an instance of the message handler.
The message handler should be set as action event param `message-handler` (for CommandBus and QueryBus) or `event-listeners` (for EventBus).

As you can see command and query bus work with a single message handler whereby the event bus works with multiple listeners.
Expand All @@ -101,16 +101,18 @@ After routing the message, the message bus checks if the handler was provided as

### invoke-handler / invoke-finder (optional)

Having the message handler in place it's time to invoke it with the message. If the `message-handler` is a `callable` the `invoke-handler` action event is not triggered but instead
the handler is invoked by the message bus (true for all three bus types).
Having the message handler in place it's time to invoke it with the message. `callable` message handlers are invoked by the bus. However, the `invoke-handler` / `invoke-finder` events are always triggered.
At this stage all three bus types behave a bit different.

- CommandBus: invokes the handler with the command message, or triggers the invoke-handler action event if the handler is not a callable.
- QueryBus: much the same as the command bus but the message handler is invoked with the query message and a [deferred](https://github.com/reactphp/promise/blob/master/src/Deferred.php)
that needs to be resolved by the message handler aka finder. If the finder is not a callable the query bus triggers a `invoke-finder` action event to indicate
- CommandBus: invokes the handler with the command message. A `invoke-handler` event is triggered.
- QueryBus: much the same as the command bus but the message handler is invoked with the query message and a `React\Promise\Deferred`
that needs to be resolved by the message handler aka finder. The query bus triggers a `invoke-finder` action event to indicate
that a finder should be invoked and not a normal message handler.
- EventBus: loops over all `event-listeners` and triggers the `locate-handler` and `invoke-handler` action events for each message listener.

*Note: * The command and query bus have a mechanism to check if the command or query was handled. If not they throw an exception.
The event bus does not have such a mechanism as having no listener for an event is a valid case.

### handle-error

If at any time a plugin or the message bus itself throws an exception it is caught and passed as param `exception` to the action event. The normal action event chain breaks and a
Expand Down
Loading

0 comments on commit 5b617c2

Please sign in to comment.