PHPackages                             makinacorpus/corebus - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. makinacorpus/corebus

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

makinacorpus/corebus
====================

Command and event buses interface and logic.

2.0.0-alpha10(2y ago)05.5k↓50%GPL-2.0-or-laterPHPPHP &gt;=8.0

Since Feb 7Pushed 2y ago2 watchersCompare

[ Source](https://github.com/makinacorpus/php-corebus)[ Packagist](https://packagist.org/packages/makinacorpus/corebus)[ Docs](http://github.com/makinacorpus/php-corebus)[ RSS](/packages/makinacorpus-corebus/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (19)Versions (40)Used By (0)

CoreBus - Command and Event buses interfaces
============================================

[](#corebus---command-and-event-buses-interfaces)

Discrete command bus and domain event dispatcher interfaces for message based architectured projects.

Discrete means that your domain code will not be tainted by this component hard-dependency, aside attributes used for targeting command handler methods and event listener methods. Your domain business code remains dependency-free.

Event bus features:

- Internal synchronous event dispatcher.
- Event listener locator based upon PHP attributes.
- Event listener locator fast dumped PHP cache.
- External event dispatcher (unplugged yet).

Command bus features:

- Transactional synchronous command bus that handles your transactions.
- Event buffering during transactions, which flushes events to the external event dispatcher only in case of transaction success.
- Command handler locator based upon PHP attributes.
- Command handler locator fast dumped PHP cache.
- Default transaction implementation using `makinacorpus/goat-query`.
- Command asynchronous dispatcher with implementation that plugs to `makinacorpus/message-broker` message broker interface.

Other various features:

- Worker object for consuming asynchronous events in CLI.
- Symfony integration for everything, including console commands for the command bus worker.
- Global attributes for aspect-driven domain code configuration.
- Simple command bus interface.

Design
======

[](#design)

Basic design
------------

[](#basic-design)

Expected runtime flow of your application is the following:

- Commands may be dispatched to trigger writes in the system.
- Commands are always asynchronously handled, they may return a response.
- One command implies one transaction on your database backend.
- During a single command processing, the domain code may raise one or many domain events.
- Domain events are always dispatched synchronously within your domain code, within the triggering command transaction.

During the whole command processing, the database transaction will be isolated if the backend permits it. Commit is all or nothing, including events being emitted and listener execution during the process.

Transaction and event buffer
----------------------------

[](#transaction-and-event-buffer)

Transaction handling will be completely hidden in the implementations, your business code will never see it, here is how it works:

- Domain events while emitted and dispatched internally are stored along the way into a volatile in-memory temporary buffer.
- Once command is comsumed and task has ended, transaction will commit.
- In case of success, buffer is flushed and events may be sent to a bus for external application to listen to.
- In case of failure, transaction rollbacks, event buffer is emptied, events are discarded without further action.

Transactions can be disabled on a per-command basis, using PHP attributes on the command class.

Optional event store
--------------------

[](#optional-event-store)

If required for your project, you may plug an event store on the event dispatcher. Two options are possible:

- Plug in into the internal event dispatcher, events will be stored along the way, this requires that the event store works on the same database transaction, hence connection, than your domain repositories.
- Plug in into the event buffer output, which means events will be stored after commit, there is no consistency issues anymore, but if event storage procedure fails, you will loose history.

Internal workflow of commands
-----------------------------

[](#internal-workflow-of-commands)

### Command dispatcher

[](#command-dispatcher)

Command dispatcher is the user facing command bus for users. It has two existing variations:

- `MakinaCorpus\CoreBus\CommandBus\CommandBus` which is the default command bus interface.
- `MakinaCorpus\CoreBus\CommandBus\SynchronousCommandbus`, which extends the former. It is the exact same interface, but exists for dependency injection purpose: using the synchronous command bus will always passthrough the command directly from the dispatcher to the consumer, which will make messages being consumed synchronously.

All implementations implement both interfaces, the dependency injection component has the reponsability to distinguish the synchronous from the asynchronous implementation.

It is also the place where command access control will happen throught a decorator, as documented later in this document.

### Command consumer

[](#command-consumer)

Command consumer is the local implementation that from a given command will execute it. You can find multiple implementations:

- `MakinaCorpus\CoreBus\CommandBus\Consumer\DefaultCommandConsumer` is the real implementation, that does the handler function lookup and execute it.
- `MakinaCorpus\CoreBus\CommandBus\Consumer\TransactionalCommandConsumer` is an implementation that will decorate the default consumer, and wrap handler execution into a single database transaction, raising domain events along the way, and rollback in case of any error.
- `MakinaCorpus\CoreBus\CommandBus\Consumer\NullCommandConsumer` is only in use in unit tests, please ignore that implementation. It may come useful to to you if for any reason you need a dummy implementation.

For now there is a step which hasn't made generic yet, the command bus worker which get messages from the queue and send those to the message consumer: it is hard wired to the `makinacorpus/message-broker` package.

Implementations
---------------

[](#implementations)

Two implementations are provided:

- In-memory bus, along with null transaction handling (no transaction at all) ideal for prototyping and unit-testing.
- PostgreSQL bus implementation using `makinacorpus/goat` for message broker, and `makinacorpus/goat-query` for transaction handling using the same database connection, reliable and guaranteing data consistency.

Everything is hidden behind interfaces and different implementations are easy to implement. Your projects are not required to choose either one of those implementations, in the opposite, is encouraged implementing its own.

Setup
=====

[](#setup)

Standalone
----------

[](#standalone)

There is no standalone setup guide for now. Refer to provided Symfony configuration for a concrete example.

Symfony
-------

[](#symfony)

Simply enable the bundle in your `config/bundles.php` file:

```
return [
    // ... your other bunbles.
    MakinaCorpus\CoreBus\Bridge\Symfony\CoreBusBundle::class => ['all' => true],
];
```

Then cut and paste `src/Bridge/Symfony/Resources/example/corebus.sample.yaml`file into your `config/packages/` folder, and edit it.

Usage
=====

[](#usage)

Commands and events
-------------------

[](#commands-and-events)

Commands are plain PHP object and don't require any dependency.

Just write a Data Transport Object:

```
namespace App\Domain\SomeBusiness\Command;

final class SayHelloCommand
{
    public readonly string $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }
}
```

Same goes with events, so just write:

```
namespace App\Domain\SomeBusiness\Event;

final class HelloWasSaidEvent
{
    public readonly string $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }
}
```

Register handlers using base class
----------------------------------

[](#register-handlers-using-base-class)

Tie a single command handler:

```
namespace App\Domain\SomeBusiness\Handler;

use MakinaCorpus\CoreBus\CommandBus\AbstractCommandHandler;

final class SayHelloHandler extends AbstractCommandHandler
{
    /*
     * Method name is yours, you may have more than one handler in the
     * same class, do you as wish. Only important thing is to implement
     * the Handler interface (here via the AbstractHandler class).
     */
    public function do(SayHelloCommand $command)
    {
        echo "Hello, ", $command->name, "\n";

        $this->notifyEvent(new HelloWasSaidEvent($command->name));
    }
}
```

Please note that using the `AbstractCommandHandler` base class is purely optional, it's simply an helper for being able to use the event dispatcher and command bus from within your handlers.

Alternatively, if you don't require any of those, you may just:

- Either set the `#[MakinaCorpus\CoreBus\Attr\CommandHandler]` attribute on the class, case in which all of its methods will be considered as handlers.
- Either set the `#[MakinaCorpus\CoreBus\Attr\CommandHandler]` attribute on each method that is an handler.

You may also write as many event listeners as you wish, then even may emit events themselves:

```
namespace App\Domain\SomeBusiness\Listener;

use MakinaCorpus\CoreBus\EventBus\EventListener;

final class SayHelloListener implements EventListener
{
    /*
     * Method name is yours, you may have more than one handler in the
     * same class, do you as wish. Only important thing is to implement
     * the EventListener interface.
     */
    public function on(HelloWasSaidEvent $event)
    {
        $this->logger->debug("Hello was said to {name}.", ['name' => $event->name]);
    }
}
```

Same goes for event listeners, the base class is just here to help but is not required, you may just:

- Either set the `#[MakinaCorpus\CoreBus\Attr\EventListener]` attribute on the class, case in which all of its methods will be considered as listeners.
- Either set the `#[MakinaCorpus\CoreBus\Attr\EventListener]` attribute on each method that is an listener.

This requires that your services are known by the container. You have three different options for this.

First one, which is Symfony's default, autoconfigure all your services:

```
services:
    _defaults:
        autowire: true
        autoconfigure: true
        public: false

    everything:
        namespace: App\Domain\
        resource: '../src/Domain/*'
```

Or if you wish to play it subtle:

```
services:
    _defaults:
        autowire: true
        autoconfigure: true
        public: false

    handler_listener:
        namespace: App\Domain\
        resource: '../src/Domain/*/{Handler,Listener}'
```

Or if you want to do use the old ways:

```
services:
    App\Domain\SomeBusiness\Handler\SayHelloHandler: ~
    App\Domain\SomeBusiness\Listener\SayHelloListener: ~
```

In all cases, you don't require any tags or any other metadata as long as you either extend the base class, or use the attributes.

Register handlers using attributes
----------------------------------

[](#register-handlers-using-attributes)

Tie a single command handler:

```
namespace App\Domain\SomeBusiness\Handler;

use MakinaCorpus\CoreBus\EventBus\EventBusAware;
use MakinaCorpus\CoreBus\EventBus\EventBusAwareTrait;

final class SayHelloHandler implements EventBusAware
{
    use EventBusAwareTrait;

    /*
     * Method name is yours, you may have more than one handler in the
     * same class, do you as wish. Only important thing is to implement
     * the Handler interface (here via the AbstractHandler class).
     */
    #[MakinaCorpus\CoreBus\Attr\CommandHandler]
    public function do(SayHelloCommand $command)
    {
        echo "Hello, ", $command->name, "\n";

        $this->notifyEvent(new HelloWasSaidEvent($command->name));
    }
}
```

You may also write as many event listeners as you wish, then even may emit events themselves:

```
namespace App\Domain\SomeBusiness\Listener;

final class SayHello
{
    /*
     * Method name is yours, you may have more than one handler in the
     * same class, do you as wish. Only important thing is to implement
     * the EventListener interface.
     */
    #[MakinaCorpus\CoreBus\Attr\EventListener]
    public function on(HelloWasSaidEvent $event)
    {
        $this->logger->debug("Hello was said to {name}.", ['name' => $event->name]);
    }
}
```

Using Symfony container machinery, no configuration is needed for this to work.

Symfony commands
================

[](#symfony-commands)

Push a message into the bus
---------------------------

[](#push-a-message-into-the-bus)

Pushing a message is as simple as:

```
bin/console corebus:push CommandName
