Installation

Event Engine is not a full stack framework. Instead you integrate it in any PHP framework that supports PHP Standards Recommendations.

Skeleton

The easiest way to get started is by using the skeleton. It ships with a preconfigured Event Engine, a recommended project structure, ready-to-use docker containers and Zend Strategility to handle HTTP requests.

Again: The skeleton is not the only way to set up Event Engine. You can tweak set up as needed and integrate Event Engine with Symfony, Laravel or any other framework or middleware dispatcher.

Required Infrastructure

Event Engine is based on PHP 7.2 or higher. Package dependencies are installed using composer.

Database

By default Event Engine uses prooph/event-store to store events recorded by the write model and a DocumentStore (see "Document Store" chapter) to store the read model.

The skeleton uses prooph's Postgres event store and a Postgres Document Store implementation. This allows Event Engine to work with a single database, but that's not a requirement. You can mix and match. Event Engine defines a lean event store interface, that can be found in the event-engine/php-event-store package. Projections don't have a hard dependency on the document store, either. A document store is only required when using the MultiModelStore feature or the default aggregate projection. Other than that, you can use whatever you want to persist the read model.

Creating The Event Stream

By default all events are stored in a single stream and prooph/event-store has to be set up with the SingleStreamStrategy! The reason for this is that projections rely on a guaranteed order of events. A single stream is the only way to fulfill this requirement.

When using a relational database as an event store a single table is also very efficient. A longer discussion about the topic can be found in the prooph/pdo-event-store repo.

An easy way to create the needed stream is to use the event store API directly.

<?php
declare(strict_types=1);

namespace EventEngine;

use ArrayIterator;
use Prooph\EventStore\EventStore;
use Prooph\EventStore\Stream;
use Prooph\EventStore\StreamName;

chdir(dirname(__DIR__));

require_once 'vendor/autoload.php';

$container = require 'config/container.php';

/** @var EventStore $eventStore */
$eventStore = $container->get(EventStore::class);
$eventStore->create(new Stream(new StreamName('event_stream'), new ArrayIterator()));

echo "done.\n";

Such a script is used in the skeleton. As you can see we request the event store from a container that we get from a config file. The skeleton uses Zend Strategility and this is a common approach in Strategility (and Zend Expressive) based applications.

If you want to use another framework, adopt the script accordingly. The only thing that really matters is that you get a configured prooph/event-store from the PSR-11 container used by Event Engine.

Read Model Storage

Projection storage is set up on the fly. You don't need to prepare it upfront, but you can if you prefer to work with a database migration tool. It is up to you. Learn more about read model storage set up in the projections chapter.

The Multi-Model-Store requires existing read model collections. Please find a detailed explanation in the tutorial.

Descriptions

Event Engine is bootstrapped in three phases. Descriptions are loaded first, followed by a $eventEngine->initialize(/* dependencies */) call. Finally, $eventEngine->bootstrap($env, $debugMode) prepares the system so that it can handle incoming messages.

Bootstrapping is split because description and initialization phase can be skipped in production. Read more about this in Production Optimization.

A "zero configuration" approach is used. While you have to configure integrated packages like prooph/event-store, Event Engine itself does not require centralized configuration. Instead it loads so called Event Engine Descriptions:

<?php

declare(strict_types=1);

namespace Prooph\EventEngine;

interface EventEngineDescription
{
    public static function describe(EventEngine $eventEngine): void;
}

Any class implementing the interface can be loaded by Event Engine. The task of a Description is to tell Event Engine how the application is structured. This is done in a programmatic way using Event Engine's registration API which we will cover in the next chapter. Here is a simple example of a Description that registers a command in Event Engine.

<?php
declare(strict_types=1);


namespace App\Api;

use Prooph\EventEngine\EventEngine;
use Prooph\EventEngine\EventEngineDescription;

class Command implements EventEngineDescription
{
    const COMMAND_CONTEXT = 'MyContext.';
    const REGISTER_USER = self::COMMAND_CONTEXT . 'RegisterUser';

    /**
     * @param EventEngine $eventEngine
     */
    public static function describe(EventEngine $eventEngine): void
    {
         $eventEngine->registerCommand(
            self::REGISTER_USER,  //<-- Name of the  command defined as constant above
            JsonSchema::object([
                Payload::USER_ID => Schema::userId(),
                Payload::USERNAME => Schema::username(),
                Payload::EMAIL => Schema::email(),
            ])
         );

    }
}

Now we only need to tell Event Engine that it should load the Description:

declare(strict_types=1);

require_once 'vendor/autoload.php';

$eventEngine = new EventEngine(
    new OpisJsonSchema() /* Or another Schema implementation */
);

$eventEngine->load(App\Api\Command::class);

Event Engine only requires a EventEngine\Schema\Schema implementation in the constructor. All other dependencies are passed during initialize phase (see next section).

Initialize

Event Engine needs to aggregate information from all Descriptions. This is done in the Initialize phase. The phase also requires mandatory and optional dependencies used by Event Engine. Details are listed on the Dependencies page.

Bootstrap

Last, but not least $eventEngine->bootstrap($environment, $debugMode) starts the engine and we're ready to take off. Event Engine supports 3 different environments: dev, prod and test. The environment is mainly used to set up third-party components like a logger.

Same is true for the debug mode. It can be used to enable verbose logging or displaying of exceptions even if Event Engine runs in prod environment. You have to take care of this when setting up services. Event Engine just provides the information:

Environment: $eventEngine->env(); // prod | dev | test
Debug Mode: $eventEngine->debugMode(); // bool
Fork me on GitHub