2017 © Pedro Peláez
 

library domain-event-bundle

Bundle to create the domain layer of your DDD application

image

gpslab/domain-event-bundle

Bundle to create the domain layer of your DDD application

  • PHP
  • 0 Dependents
  • 0 Suggesters
  • 0 Forks
  • 0 Open issues
  • 12 Versions
  • 58 % Grown

The README.md

Latest Stable Version PHP Version Support Total Downloads Build Status Coverage Status Scrutinizer Code Quality License, (*1)

Domain event bundle

Bundle to create the domain layer of your Domain-driven design (DDD) application., (*2)

This Symfony bundle is a wrapper for gpslab/domain-event, look it for more details., (*3)

Installation

Pretty simple with Composer, run:, (*4)

composer req gpslab/domain-event-bundle

Configuration

Example configuration, (*5)

gpslab_domain_event:
    # Event bus service
    # Support 'listener_located', 'queue' or a custom service
    # As a default used 'listener_located'
    bus: 'listener_located'

    # Event queue service
    # Support 'pull_memory', 'subscribe_executing' or a custom service
    # As a default used 'pull_memory'
    queue: 'pull_memory'

    # Event listener locator
    # Support 'symfony', 'container', 'direct_binding' or custom service
    # As a default used 'symfony'
    locator: 'symfony'

    # Publish domain events post a Doctrine flush event
    # As a default used 'false'
    publish_on_flush: true

Usage

Create a domain event, (*6)

use GpsLab\Domain\Event\Event

class PurchaseOrderCreatedEvent implements Event
{
    private $customer_id;
    private $create_at;

    public function __construct(CustomerId $customer_id, \DateTimeImmutable $create_at)
    {
        $this->customer_id = $customer_id;
        $this->create_at = $create_at;
    }

    public function customerId(): CustomerId
    {
        return $this->customer_id;
    }

    public function createAt(): \DateTimeImmutable
    {
        return $this->create_at;
    }
}

Raise your event, (*7)

use GpsLab\Domain\Event\Aggregator\AbstractAggregateEvents;

final class PurchaseOrder extends AbstractAggregateEvents
{
    private $customer_id;
    private $create_at;

    public function __construct(CustomerId $customer_id)
    {
        $this->customer_id = $customer_id;
        $this->create_at = new \DateTimeImmutable();

        $this->raise(new PurchaseOrderCreatedEvent($customer_id, $this->create_at));
    }
}

Create listener, (*8)

use GpsLab\Domain\Event\Event;

class SendEmailOnPurchaseOrderCreated
{
    private $mailer;

    public function __construct(\Swift_Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    public function onPurchaseOrderCreated(PurchaseOrderCreatedEvent $event): void
    {
        $message = $this->mailer
            ->createMessage()
            ->setTo('recipient@example.com')
            ->setBody(sprintf(
                'Purchase order created at %s for customer #%s',
                $event->createAt()->format('Y-m-d'),
                $event->customerId()
            ));

        $this->mailer->send($message);
    }
}

Register event listener, (*9)

services:
    SendEmailOnPurchaseOrderCreated:
        arguments: [ '@mailer' ]
        tags:
            - { name: domain_event.listener, event: PurchaseOrderCreatedEvent, method: onPurchaseOrderCreated }

Publish events in listener, (*10)

use GpsLab\Domain\Event\Bus\EventBus;

// get event bus from DI container
$bus = $this->get(EventBus::class);

// do what you need to do on your Domain
$purchase_order = new PurchaseOrder(new CustomerId(1));

// this will clear the list of event in your AggregateEvents so an Event is trigger only once
$events = $purchase_order->pullEvents();

// You can have more than one event at a time.
foreach($events as $event) {
    $bus->publish($event);
}

// You can use one method
//$bus->pullAndPublish($purchase_order);

Listener method name

You do not need to specify the name of the event handler method. By default, the __invoke method is used., (*11)

use GpsLab\Domain\Event\Event;

class SendEmailOnPurchaseOrderCreated
{
    private $mailer;

    public function __construct(\Swift_Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    public function __invoke(PurchaseOrderCreatedEvent $event): void
    {
        $message = $this->mailer
            ->createMessage()
            ->setTo('recipient@example.com')
            ->setBody(sprintf(
                'Purchase order created at %s for customer #%s',
                $event->createAt()->format('Y-m-d'),
                $event->customerId()
            ));

        $this->mailer->send($message);
    }
}

Register event listener, (*12)

services:
    SendEmailOnPurchaseOrderCreated:
        arguments: [ '@mailer' ]
        tags:
            - { name: domain_event.listener, event: PurchaseOrderCreatedEvent }

Event subscribers

Create subscriber, (*13)

use GpsLab\Domain\Event\Event;
use GpsLab\Domain\Event\Listener\Subscriber;

class SendEmailOnPurchaseOrderCreated implements Subscriber
{
    private $mailer;

    public function __construct(\Swift_Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    public static function subscribedEvents(): array
    {
        return [
            PurchaseOrderCreatedEvent::class => ['onPurchaseOrderCreated'],
        ];
    }

    public function onPurchaseOrderCreated(PurchaseOrderCreatedEvent $event): void
    {
        $message = $this->mailer
            ->createMessage()
            ->setTo('recipient@example.com')
            ->setBody(sprintf(
                'Purchase order created at %s for customer #%s',
                $event->createAt()->format('Y-m-d'),
                $event->customerId()
            ));

        $this->mailer->send($message);
    }
}

Register event subscriber, (*14)

services:
    SendEmailOnPurchaseOrderCreated:
        arguments: [ '@mailer' ]
        tags:
            - { name: domain_event.subscriber }

Use pull Predis queue

Install Predis with Composer, run:, (*15)

composer require predis/predis

Register services:, (*16)

services:
    # Predis
    Predis\Client:
        arguments: [ '127.0.0.1' ]

    # Events serializer for queue
    GpsLab\Domain\Event\Queue\Serializer\SymfonySerializer:
        arguments: [ '@serializer', 'json' ]

    # Predis event queue
    GpsLab\Domain\Event\Queue\Pull\PredisPullEventQueue:
        arguments:
            - '@Predis\Client'
            - '@GpsLab\Domain\Event\Queue\Serializer\SymfonySerializer'
            - '@logger'
            - 'event_queue_name'

Change config for use custom queue:, (*17)

gpslab_domain_event:
    queue: 'GpsLab\Domain\Event\Queue\Pull\PredisPullEventQueue'

And now you can use custom queue:, (*18)

use GpsLab\Domain\Event\Queue\EventQueue;

$container->get(EventQueue::class)->publish($domain_event);

In latter pull events from queue:, (*19)

use GpsLab\Domain\Event\Queue\EventQueue;

$queue = $container->get(EventQueue::class);
$bus = $container->get(EventQueue::class);

while ($event = $queue->pull()) {
    $bus->publish($event);
}

Use Predis subscribe queue

Install Predis PubSub adapter with Composer, run:, (*20)

composer require superbalist/php-pubsub-redis

Register services:, (*21)

services:
    # Predis
    Predis\Client:
        arguments: [ '127.0.0.1' ]

    # Predis PubSub adapter
    Superbalist\PubSub\Redis\RedisPubSubAdapter:
        arguments: [ '@Predis\Client' ]

    # Events serializer for queue
    GpsLab\Domain\Event\Queue\Serializer\SymfonySerializer:
        arguments: [ '@serializer', 'json' ]

    # Predis event queue
    GpsLab\Domain\Event\Queue\Subscribe\PredisSubscribeEventQueue:
        arguments:
            - '@Superbalist\PubSub\Redis\RedisPubSubAdapter'
            - '@GpsLab\Domain\Event\Queue\Serializer\SymfonySerializer'
            - '@logger'
            - 'event_queue_name'

Change config for use custom queue:, (*22)

gpslab_domain_event:
    queue: 'GpsLab\Domain\Event\Queue\Subscribe\PredisSubscribeEventQueue'

And now you can use custom queue:, (*23)

use GpsLab\Domain\Event\Queue\EventQueue;

$container->get(EventQueue::class)->publish($domain_event);

Subscribe on the queue:, (*24)

use GpsLab\Domain\Event\Queue\EventQueue;

$container->get(EventQueue::class)->subscribe(function (Event $event) {
    // do somthing
});

Note, (*25)

You can use subscribe handlers as a services and tag it for optimize register., (*26)

Many queues

You can use many queues for separation the flows. For example, you want to handle events of different Bounded Contexts separately from each other., (*27)

services:
    acme.domain.purchase_order.event.queue:
        class: GpsLab\Domain\Event\Queue\Pull\PredisPullEventQueue
        arguments:
            - '@Superbalist\PubSub\Redis\RedisPubSubAdapter'
            - '@GpsLab\Domain\Event\Queue\Serializer\SymfonySerializer'
            - '@logger'
            - 'purchase_order_event_queue'

    acme.domain.article_comment.event.queue:
        class: GpsLab\Domain\Event\Queue\Pull\PredisPullEventQueue
        arguments:
            - '@Superbalist\PubSub\Redis\RedisPubSubAdapter'
            - '@GpsLab\Domain\Event\Queue\Serializer\SymfonySerializer'
            - '@logger'
            - 'article_comment_event_queue'

And now you can use a different queues., (*28)

In Purchase order Bounded Contexts., (*29)

$event = new PurchaseOrderCreatedEvent(
    new CustomerId(1),
    new \DateTimeImmutable()
);

$container->get('acme.domain.purchase_order.event.queue')->publish($event);

In Article comment Bounded Contexts., (*30)

$event = new ArticleCommentedEvent(
    new ArticleId(1),
    new AuthorId(1),
    $comment
    new \DateTimeImmutable()
);

$container->get('acme.domain.article_comment.event.queue')->publish($event);

Note, (*31)

Similarly, you can split the subscribe queues., (*32)

License

This bundle is under the MIT license. See the complete license in the file: LICENSE, (*33)

The Versions