+ All Categories
Home > Internet > CQRS + Event Sourcing in PHP

CQRS + Event Sourcing in PHP

Date post: 16-Mar-2018
Category:
Upload: manuel-lopez-torrent
View: 542 times
Download: 5 times
Share this document with a friend
134
CQRS Command Query Responsibility Segregation
Transcript
Page 1: CQRS + Event Sourcing in PHP

CQRSCommand Query Responsibility Segregation

Page 2: CQRS + Event Sourcing in PHP

Manel López Torrent

Ingeniero informáticaDesarrollo software

Agile

@[email protected]

Page 3: CQRS + Event Sourcing in PHP
Page 4: CQRS + Event Sourcing in PHP

EDUMENT

http://cqrs.nu

DDD in PHP

https://github.com/dddinphp/blog-cqrs

https://github.com/dddinphp/last-wishes-gamify

Page 5: CQRS + Event Sourcing in PHP

¿CQRS?

Page 6: CQRS + Event Sourcing in PHP

CQRSPatrón de diseño de app

Lecturas / Escrituras

Page 7: CQRS + Event Sourcing in PHP

Modelo rico

Mejor rendimiento

Mejor escalabilidad

Objetivos

Page 8: CQRS + Event Sourcing in PHP

Greg Younghttps://goodenoughsoftware.net/@gregyoung

Page 9: CQRS + Event Sourcing in PHP

DDD (Domain Driven Design)

Modelo rico VS Modelo Anémico

Patrones tácticos

Arquitectura de capas

Arquitectura hexagonal

Requisitos

Page 10: CQRS + Event Sourcing in PHP

Cafetería

Page 11: CQRS + Event Sourcing in PHP

- Cuandos los clientes entran en el café se sientan en una mesa , un camarero/a , abre una cuenta para esa mesa

- Los clientes pueden ordenar bebida y/o comidas del menú

- Una vez ordenadas las bebidas pueden ser servidas de inmediato.

- La comida debe ser preparada en cocina. Una vez se ha preparado puede ser servida

- Cuando los clientes terminan de comer pagan la cuenta ( pueden dejar propina ) y la cuenta se cierra

- No se puede cerrar una cuenta si hay bebida o comida pendientes

Page 12: CQRS + Event Sourcing in PHP

Abrir

Ordenar

Preparar

Servir

Pagar

Cerrar

Camarero

Mesa

Cuenta

Bebida / Comida

Page 13: CQRS + Event Sourcing in PHP
Page 14: CQRS + Event Sourcing in PHP

Trasladar el modelo a objetos ( DDD táctico )

Creamos tests para asegurar su corrección

Arquitectura hexagonal para conectar el modelo con el mundo

exterior

Page 15: CQRS + Event Sourcing in PHP

Application Layer

DomainModel

ApplicationService 1

Request

InfrastructureLayer DataStoreApplication

Service 2

ApplicationService n

Response

Page 16: CQRS + Event Sourcing in PHP

<?php

class Tab

{

static public function open($table, $waiter): Tab {}

public function placeOrder($orderedItems) {}

public function serveDrinks($drinksServed) {}

public function prepareFood($foodPrepared) {}

public function serveFood($foodServed) {}

public function close(float $amount) {}

}

interface TabRepository

{

public function getById(TabId $tabId);

public function save(Tab $tab);

}

Page 17: CQRS + Event Sourcing in PHP

<?php

class OrderedItem

{

public function __construct(int $menuNumber, bool $IsDrink, float $price) {}

public function getMenuNumber(): int {}

public function isDrink(): bool {}

public function getPrice(): float {}

}

interface OrderedItemsRepository

{

public function findById($id): OrderedItem;

}

Page 18: CQRS + Event Sourcing in PHP

OK

Page 19: CQRS + Event Sourcing in PHP

Nuevos requisitos

Page 20: CQRS + Event Sourcing in PHP

Maître

Barman

Cocina

Page 21: CQRS + Event Sourcing in PHP

<?php

interface TabRepository

{

public function getById(TabId $tabId);

public function save(Tab $tab);

}

Page 22: CQRS + Event Sourcing in PHP

<?php

interface TabRepository

{

public function getById(TabId $tabId);

public function save(Tab $tab);

public function getTabsByWaiter($waiter);

public function getTabsWithDrinkPending();

public function getTabsWithFoodPrepared();

public function getTabsOpen();

public function getTabsClosed(DateTime $date);

}

Page 23: CQRS + Event Sourcing in PHP

<?php

use Doctrine\ORM\Query

interface TabRepository

{

public function getById(TabId $tabId);

public function save(Tab $tab);

public function getTabsByQuery(Query $query)

}

Page 24: CQRS + Event Sourcing in PHP

<?php

interface TabRepository

{

public function getById(TabId $tabId);

public function save(Tab $tab);

public function getTabsBySpeficication(Specification $s);

}

Page 25: CQRS + Event Sourcing in PHP

<?php

class MysqlTabRepository implements TabRepository { ... }

class RedisTabRepository implements TabRepository { ... }

class MongoDBTabRepository implements TabRepository { ... }

¿?

Page 26: CQRS + Event Sourcing in PHP

ORM

Frameworks

Page 27: CQRS + Event Sourcing in PHP

Contaminamos el modelo

Page 28: CQRS + Event Sourcing in PHP

SRP

Page 29: CQRS + Event Sourcing in PHP

SRP

Page 30: CQRS + Event Sourcing in PHP

Command Query Responsibility Segregation

Page 31: CQRS + Event Sourcing in PHP

Read Model Write Model

Page 32: CQRS + Event Sourcing in PHP

Read Model Write Model

Lógica de negocio

Page 33: CQRS + Event Sourcing in PHP

Read Model Write Model

Query Command

Page 34: CQRS + Event Sourcing in PHP

Commands

OpenTab

PlaceOrder

MarkDrinksServed

MarkFoodPrepared

MarkFoodServed

CloseTab

Querys

AllTabs

OneTab

AllTabsByWaiter

Page 35: CQRS + Event Sourcing in PHP

InfrastructureLayer

Application Layer

CommandHandlerCommand

CommandHandler

QueryHandle

Read Model

Model

Command

QueryDataStore

Response

QueryHandle

Query

Response

Page 36: CQRS + Event Sourcing in PHP

Controladores Comando Bus Command Handler

Page 37: CQRS + Event Sourcing in PHP

CommandCommandHandler

Page 38: CQRS + Event Sourcing in PHP

class OpenTabCommand

{

private $tabId;

private $tableNumber;

private $waiterId;

public function __construct($tabId, $tableNumber, $waiterId)

{

$this->tabId = $tabId;

$this->tableNumber = $tableNumber;

$this->waiterId = $waiterId;

}

public function getTabId() { }

public function getTableNumber() { }

public function getWaiterId() { }

}

Page 39: CQRS + Event Sourcing in PHP

class OpenTabHandler

{

private $tabRepopsitory;

public function __construct(TabRepository $tabRepository)

{

$this->tabRepopsitory = $tabRepository;

}

public function handle(OpenTabCommand $command)

{

$newTab = Tab::openWithId(

TabId::fromString($command->getTabId()),

$command->getTableNumber(),

$command->getWaiterId()

);

$this->tabRepopsitory->add($newTab);

}

}

Page 40: CQRS + Event Sourcing in PHP

QueryQueryHandler

Page 41: CQRS + Event Sourcing in PHP

class OneTabQuery

{

public $id;

public function __construct($id)

{

$this->id = $id;

}

}

Page 42: CQRS + Event Sourcing in PHP

class OneTabQueryHandler

{

private $tabsRepository;

private $dataTransformer;

public function __construct(

TabsRepository $tabsRepostiory,

DataTranformer $dataTransformer

) {

$this->tabsRepository = $tabsRepostiory;

$this->dataTransformer = $dataTransformer;

}

public function handle(OneTabQuery $query)

{

$tab = $this->tabsRepository->find($query->id);

$this->dataTransformer->write($tab);

return $this->dataTransformer->read();

}

}

Page 43: CQRS + Event Sourcing in PHP

Modelo Escritura

Tab

TabRepository

OrderedItem

OrderedItemRepository

Modelo Lectura

TabView

TabViewRepository

OrderedItemView

OrderedItemViewRepository

Page 44: CQRS + Event Sourcing in PHP

Modelo Escritura

Tab

TabRepository

OrderedItem

OrderedItemRepository

Modelo Lectura

TabView

TabViewRepository

OrderedItemView

OrderedItemViewRepository

Lógica neg

ocio Modelo

Anémico

Page 45: CQRS + Event Sourcing in PHP

<?php

interface TabRepository

{

public function getById(TabId $tabId);

public function save(Tab $tab);

}

interface TabViewRepository

{

public function getTabsByWaiter($waiter);

public function getTabsWithDrinkPending();

public function getTabsWithFoodPrepared();

public function getTabsOpen();

public function getTabsClosed(DateTime $date);

}

Page 46: CQRS + Event Sourcing in PHP

Modelo rico

Mejor rendimiento

Mejor escalabilidad

Page 47: CQRS + Event Sourcing in PHP

¿Mejor rendimiento?

Page 48: CQRS + Event Sourcing in PHP

InfrastructureLayer

Application Layer

CommandHandlerCommand

CommandHandler

QueryHandle

Read Model

Model

Command

QueryDataStore

Response

QueryHandle

Query

Response

Page 49: CQRS + Event Sourcing in PHP

Model InfrastructureLayer

DataStoreInfrastructure

Layer

DataStore

Application Layer

CommandHandlerCommand

CommandHandler

QueryHandle

Read Model

Command

Query

Response

QueryHandle

Query

Response

Page 50: CQRS + Event Sourcing in PHP

<?php

class RedisTabRepository implements TabRepository { ... }

class MysqlTabViewRepository implements TabViewRepository { ... }

Page 51: CQRS + Event Sourcing in PHP

Modelo rico

Mejor rendimiento

Mejor escalabilidad

Page 52: CQRS + Event Sourcing in PHP

¿Mejor escalabilidad?

Page 53: CQRS + Event Sourcing in PHP

Model InfrastructureLayer

DataStoreInfrastructure

Layer

DataStore

Application Layer

CommandHandlerCommand

CommandHandler

QueryHandle

Read Model

Command

Query

Response

QueryHandle

Query

Response

WRITE SERVICE

READ SERVICE

Page 54: CQRS + Event Sourcing in PHP

APIGATEWA

Y

READSERVICE

WRITESERVICE

READSERVICE

READSERVICE

Page 55: CQRS + Event Sourcing in PHP

Modelo rico

Mejor rendimiento

Mejor escalabilidad

Page 56: CQRS + Event Sourcing in PHP

¿Consistencia de los datos?

Page 57: CQRS + Event Sourcing in PHP

InfrastructureLayer

Application Layer

CommandHandlerCommand

CommandHandler

QueryHandle

Read Model

Model

Command

Query

DataStore

Response

QueryHandle

Query

Response

InfrastructureLayer

DataStore

Page 58: CQRS + Event Sourcing in PHP

Eventos del dominio

Page 59: CQRS + Event Sourcing in PHP

Se ha abierto una cuenta

Se ordenan bebidas y comida

La comida está preparada

Las bebidas se han servido

La comida se ha preparado

La comida se ha servidor

Se ha cerrado una cuenta

Page 60: CQRS + Event Sourcing in PHP

TabOpened

DrinksOrdered

FoodOrdered

DrinksServed

FoodPrepared

FoodServed

TabClosed

Page 61: CQRS + Event Sourcing in PHP

<?php

class DrinksOrdered extends TabEvent

{

private $items;

public function __construct(TabId $id, $items)

{

$this->id = $id;

$this->items = $items;

}

public function getItems()

{

return $this->items;

}

}

Page 62: CQRS + Event Sourcing in PHP

Command Model

Page 63: CQRS + Event Sourcing in PHP

Evento

BU

SModel

Evento

Evento

Page 64: CQRS + Event Sourcing in PHP

Model Evento Listenner

BU

S

Page 65: CQRS + Event Sourcing in PHP

DataStore

Model Listenner

BU

S

Page 66: CQRS + Event Sourcing in PHP

Event sourcing

Page 67: CQRS + Event Sourcing in PHP

El estado de nuestro sistema

es la suma de todos los eventos

ocurridos en el.

Page 68: CQRS + Event Sourcing in PHP

A1 {x = 1y = 2

}ID X Y

A1 1 2

A2 3 5 A2 {x = 3y = 5

}

Page 69: CQRS + Event Sourcing in PHP

A2, y = 5

A1, y = 2

A2, x = 3

A1 , y = 7

A2, x = null y = null

A1 , x = 1

A1, x = null y = null

A1 {x = nully = null

}

Tiempo

Page 70: CQRS + Event Sourcing in PHP

A2, y = 5

A1, y = 2

A2, x = 3

A1 , y = 7

A2, x = null y = null

A1 , x = 1

A1, x = null y = null

A1 {x = nully = null

}

A1 {x = 1y = null

}

Tiempo

Page 71: CQRS + Event Sourcing in PHP

A2, y = 5

A1, y = 2

A2, x = 3

A1 , y = 7

A2, x = null y = null

A1 , x = 1

A1, x = null y = null

A1 {x = nully = null

}

A1 {x = 1y = null

}

A2 {x = nully = null

}

Tiempo

Page 72: CQRS + Event Sourcing in PHP

A2, y = 5

A1, y = 2

A2, x = 3

A1 , y = 7

A2, x = null y = null

A1 , x = 1

A1, x = null y = null

A1 {x = nully = null

}

A1 {x = 1y = null

}

A1 {x = 1y = 7

}

A2 {x = nully = null

}

Tiempo

Page 73: CQRS + Event Sourcing in PHP

A2, y = 5

A1, y = 2

A2, x = 3

A1 , y = 7

A2, x = null y = null

A1 , x = 1

A1, x = null y = null

A1 {x = nully = null

}

A1 {x = 1y = null

}

A1 {x = 1y = 7

}

A2 {x = nully = null

}

A2 {x = 3y = null

}

Tiempo

Page 74: CQRS + Event Sourcing in PHP

A2, y = 5

A1, y = 2

A2, x = 3

A1 , y = 7

A2, x = null y = null

A1 , x = 1

A1, x = null y = null

A1 {x = nully = null

}

A1 {x = 1y = null

}

A1 {x = 1y = 7

}

A1 {x = 1y = 2

}

A2 {x = nully = null

}

A2 {x = 3y = null

}

Tiempo

Page 75: CQRS + Event Sourcing in PHP

A2, y = 5

A1, y = 2

A2, x = 3

A1 , y = 7

A2, x = null y = null

A1 , x = 1

A1, x = null y = null

A1 {x = nully = null

}

A1 {x = 1y = null

}

A1 {x = 1y = 7

}

A1 {x = 1y = 2

}

A2 {x = nully = null

}

A2 {x = 3y = null

}

A2 {x = 3y = 5

}

Tiempo

Page 76: CQRS + Event Sourcing in PHP

A2, y = 5

A1, y = 2

A2, x = 3

A1 , y = 7

A2, x = null y = null

A1 , x = 1

A1, x = null y = null

A1 {x = nully = null

}

A1 {x = 1y = null

}

A1 {x = 1y = 7

}

A1 {x = 1y = 2

}

A2 {x = nully = null

}

A2 {x = 3y = null

}

A2 {x = 3y = null

}

Page 77: CQRS + Event Sourcing in PHP

Historia

Auditoria

Fácil persistencia

Page 78: CQRS + Event Sourcing in PHP

Reconstruimos nuestros agregado a partir de un

flujo de eventos

Page 79: CQRS + Event Sourcing in PHP

Command Model

Page 80: CQRS + Event Sourcing in PHP

Evento

BU

SModel

Evento

Evento

EventStore

Page 81: CQRS + Event Sourcing in PHP

Proyecciones

Page 82: CQRS + Event Sourcing in PHP

ID X Y

A1 1 2

A2 3 5

A1, y = 2

A1 , y = 7

A1 , x = 1

A3, y = 5

A2, x = 3

Page 83: CQRS + Event Sourcing in PHP

ID X Y

A1 1 2

A2 3 5

A1, x = 2

A1, y = 2

A1 , y = 7

A1 , x = 1

A3, y = 5

A2, x = 3

Page 84: CQRS + Event Sourcing in PHP

ID X Y

A1 1 2

A2 3 5

UPDATE table_name SET x=2 WHERE id = A1 A1, x = 2

A1, y = 2

A1 , y = 7

A1 , x = 1

A3, y = 5

A2, x = 3

Page 85: CQRS + Event Sourcing in PHP

ID X Y

A1 2 2

A2 3 5

A1, x = 2

A1, y = 2

A1 , y = 7

A1 , x = 1

Page 86: CQRS + Event Sourcing in PHP

Inconsistencia Eventual

Page 87: CQRS + Event Sourcing in PHP

¿Dónde generamos los eventos?

Page 88: CQRS + Event Sourcing in PHP

Agregados

Page 89: CQRS + Event Sourcing in PHP

Entidad raíz

Id del agregado

Almacenar los eventos

Reconstruir desde flujo de eventos

Page 90: CQRS + Event Sourcing in PHP

<?php

class Tab

{

static public function open($table, $waiter): Tab {}

public function placeOrder($orderedItems) {}

public function serveDrinks($drinksServed) {}

public function prepareFood($foodPrepared) {}

public function serveFood($foodServed) {}

public function close(float $amount) {}

}

Page 91: CQRS + Event Sourcing in PHP

<?php

class Tab

{

// TabOpened

static public function open($table, $waiter): Tab {}

// DrinksOrdered , FoodOrdered

public function placeOrder($orderedItems) {}

// DrinksServed

public function serveDrinks($drinksServed) {}

// FoodPrepared

public function prepareFood($foodPrepared) {}

// FoodServed

public function serveFood($foodServed) {}

// TabClosed

public function close(float $amount) {}

}

Page 92: CQRS + Event Sourcing in PHP

<?php

class Tab {

static public function open($table, $waiter): Tab

{

$id = TabId::create();

$newTab = new Tab($id, $table, $waiter);

DomainEventPublisher::instance()->publish(

new TabOpened($id, $table, $waiter)

);

return $newTab;

}

}

Page 93: CQRS + Event Sourcing in PHP

<?php

class Tab {

static public function open($table, $waiter): Tab

{

$id = TabId::create();

$newTab = new Tab($id, $table, $waiter);

DomainEventPublisher::instance()->publish(

new TabOpened($id, $table, $waiter)

);

return $newTab;

}

}

Page 94: CQRS + Event Sourcing in PHP

<?php

class Tab extends Aggregate {

static public function open($table, $waiter): Tab

{

$id = TabId::create();

$newTab = new Tab($id, $table, $waiter);

$this->recordThat(new TabOpened($id, $table, $waiter));

return $newTab;

}

}

Page 95: CQRS + Event Sourcing in PHP

abstract class Aggregate implements AggregateRoot

{

private $recordedEvents = [];

protected function recordThat(DomainEvent $aDomainEvent)

{

$this->recordedEvents[] = $aDomainEvent;

}

public function getRecordedEvents(): DomainEvents

{

return new DomainEvents($this->recordedEvents);

}

public function clearRecordedEvents()

{

$this->recordedEvents = [];

}

}

Page 96: CQRS + Event Sourcing in PHP

abstract class Aggregate implements AggregateRoot

{

public static function reconstituteFrom(AggregateHistory $anAggregateHistory) {

$anAggregate = static::createEmptyWithId(

$anAggregateHistory->getAggregateId()

);

foreach ($anAggregateHistory as $anEvent) {

$anAggregate->apply($anEvent);

}

return $anAggregate;

}

private function apply($anEvent)

{

$method = 'apply' . ClassFunctions::short($anEvent);

$this->$method($anEvent);

}

}

Page 97: CQRS + Event Sourcing in PHP

class Tab extends Aggregate {

public function applyDrinksServed(DrinksServed $drinksServed)

{

array_walk($drinksServed->getItems(),

function($drinkServedNumber) {

$item = $this->outstandingDrinks[$drinkServedNumber];

unset($this->outstandingDrinks[$drinkServedNumber]);

$this->servedItems[$drinkServedNumber] = $item;

});

}

}

Page 98: CQRS + Event Sourcing in PHP

class Tab extends Aggregate {

public function applyDrinksServed(DrinksServed $drinksServed)

{

array_walk($drinksServed->getItems(),

function($drinkServedNumber) {

$item = $this->outstandingDrinks[$drinkServedNumber];

unset($this->outstandingDrinks[$drinkServedNumber]);

$this->servedItems[$drinkServedNumber] = $item;

});

}

}

Page 99: CQRS + Event Sourcing in PHP

Refactorizar agregados

Page 100: CQRS + Event Sourcing in PHP

<?php

class Tab {

public function serveDrinks($drinksServed)

{

$this->assertDrinksAreOutstanding($drinksServed);

array_walk($drinksServed, function($drinkServedNumber) {

$item = $this->outstandingDrinks[$drinkServedNumber];

unset($this->outstandingDrinks[$drinkServedNumber]);

$this->servedItems[$drinkServedNumber] = $item;

});

}

}

Page 101: CQRS + Event Sourcing in PHP

<?php

class Tab extends Aggregate {

public function serveDrinks($drinksServed)

{

$this->assertDrinksAreOutstanding($drinksServed);

array_walk($drinksServed, function($drinkServedNumber) {

$item = $this->outstandingDrinks[$drinkServedNumber];

unset($this->outstandingDrinks[$drinkServedNumber]);

$this->servedItems[$drinkServedNumber] = $item;

});

$this->recordThat(new DrinksServed(

$this->getAggregateId(),

$drinksServed

));

}

}

Page 102: CQRS + Event Sourcing in PHP

<?php

class Tab extends Aggregate {

public function applyDrinksServed(DrinksServed $drinksServed)

{

array_walk($drinksServed->getItems(),

function($drinkServedNumber) {

$item = $this->outstandingDrinks[$drinkServedNumber];

unset($this->outstandingDrinks[$drinkServedNumber]);

$this->servedItems[$drinkServedNumber] = $item;

});

}

}

Page 103: CQRS + Event Sourcing in PHP

<?php

class Tab extends Aggregate {

public function serveDrinks($drinksServed)

{

$this->assertDrinksAreOutstanding($drinksServed);

array_walk($drinksServed, function($drinkServedNumber) {

$item = $this->outstandingDrinks[$drinkServedNumber];

unset($this->outstandingDrinks[$drinkServedNumber]);

$this->servedItems[$drinkServedNumber] = $item;

});

$this->recordThat(new DrinksServed(

$this->getAggregateId(),

$drinksServed

));

}

}

Page 104: CQRS + Event Sourcing in PHP

<?php

class Tab extends Aggregate {

public function serveDrinks($drinksServed)

{

$this->assertDrinksAreOutstanding($drinksServed);

$drinksServedEvend = new DrinksServed(

$this->getAggregateId(),

$drinksServed

);

$this->recordThat($drinksServedEvend);

$this->apply($drinksServedEvend);

}

}

Page 105: CQRS + Event Sourcing in PHP

class Tab extends Aggregate {

public function serveDrinks($drinksServed)

{

$this->assertDrinksAreOutstanding($drinksServed);

$this->applyAndRecordThat(new DrinksServed(

$this->getAggregateId(),

$drinksServed

));

}

}

Page 106: CQRS + Event Sourcing in PHP

class Tab extends Aggregate {

public function serveDrinks($drinksServed)

{

$this->assertDrinksAreOutstanding($drinksServed);

$this->applyAndRecordThat(new DrinksServed(

$this->getAggregateId(),

$drinksServed

));

}

}

1 - Comprobamos que el evento se puede aplicar

Page 107: CQRS + Event Sourcing in PHP

class Tab extends Aggregate {

public function serveDrinks($drinksServed)

{

$this->assertDrinksAreOutstanding($drinksServed);

$this->applyAndRecordThat(new DrinksServed(

$this->getAggregateId(),

$drinksServed

));

}

}

1 - Comprobamos que el evento se puede aplicar2 - Lo Aplicamos y lo guardamos

Page 108: CQRS + Event Sourcing in PHP

Repositorio

Page 109: CQRS + Event Sourcing in PHP

Recuperar flujo de eventos

Persistir eventos guardados

Publicar el flujo eventos

Page 110: CQRS + Event Sourcing in PHP

<?php

interface AggregateRepository

{

public function get(IdentifiesAggregate $aggregateId):

AggregateRoot;

public function add(RecordsEvents $aggregate);

}

Page 111: CQRS + Event Sourcing in PHP

<?php

class TabEventSourcingRepository implements TabRepository

{

private $eventStore;

private $projector;

public function __construct(

EventStore $eventStore,

$projector

) {

$this->eventStore = $eventStore;

$this->projector = $projector;

}

}

Page 112: CQRS + Event Sourcing in PHP

Event Store

Page 113: CQRS + Event Sourcing in PHP

<?php

interface EventStore

{

public function commit(DomainEvents $events);

public function getAggregateHistoryFor(IdentifiesAggregate $id);

}

Page 114: CQRS + Event Sourcing in PHP

Serializar

Page 115: CQRS + Event Sourcing in PHP

{ "type": "TabOpened", "created_on": 1495579156, "data": { "id" : "8b486a7b-2e32-4e17-ad10-e90841286722", "waiter" : "Jhon Doe", "table" : 1 }}

{ "type": "DrinksOrdered", "created_on": 1495579200, "data": { "id" : "8b486a7b-2e32-4e17-ad10-e90841286722", "items" : [1,2] }}

8b486a7b-2e32-4e17-ad10-e90841286722

Page 116: CQRS + Event Sourcing in PHP

Proyecciones

Page 117: CQRS + Event Sourcing in PHP

<?php

interface Projection

{

public function eventType();

public function project($event);

}

Page 118: CQRS + Event Sourcing in PHP

<?php

class TabOpenedProjection implements Projection

{

private $pdo;

public function __construct($pdo)

{

$this->pdo = $pdo;

}

public function project($event)

{

$stmt = $this->pdo->prepare("INSERT INTO tabs (tab_id, waiter, tableNumber, open) VALUES

(:tab_id, :waiter, :tableNumber, 1)");

$stmt->execute([

':tab_id' => $event->getAggregateId(),

':waiter' => $event->getWaiterId(),

':tableNumber' => $event->getTableNumber(),

]);

}

public function eventType()

{

return TabOpened::class;

}

}

Page 119: CQRS + Event Sourcing in PHP

<?php

class Projector

{

private $projections = [];

public function register(array $projections)

{

foreach ($projections as $projection) {

$this->projections[$projection->eventType()] = $projection;

}

}

public function project(DomainEvents $events)

{

foreach ($events as $event) {

if (!isset($this->projections[get_class($event)]))

throw new NoProjectionExists();

$this->projections[get_class($event)]->project($event);

}

}

}

Page 120: CQRS + Event Sourcing in PHP

Modelo lectura

Page 121: CQRS + Event Sourcing in PHP

Modelo anémico

DTO

Entidades generadas ORM

Repositorios generados ORM

Frameworks

Page 122: CQRS + Event Sourcing in PHP
Page 123: CQRS + Event Sourcing in PHP

There are no dumb questions ...

Page 124: CQRS + Event Sourcing in PHP

https://github.com/malotor/cafe_events

PHP 7.1

Phpunit 6

Docker

Page 125: CQRS + Event Sourcing in PHP

☁ events_cafe [master] tree -L 1.├── README.md├── bootstrap.php├── build├── cache├── cli-config.php├── composer.json├── composer.lock├── coverage├── docker-compose.yml├── phpunit.xml├── public├── resources├── scripts├── src├── tests└── vendor

Page 126: CQRS + Event Sourcing in PHP

☁ events_cafe [master] tree -L 2 srcsrc├── Application│ ├── Command│ ├── DataTransformer│ └── Query├── Domain│ ├── Model│ └── ReadModel└── Infrastructure ├── CommandBus ├── Persistence ├── Serialize └── ui

Page 127: CQRS + Event Sourcing in PHP

☁ events_cafe [master] tree -L 2 src/Applicationsrc/Application├── Command│ ├── CloseTab.php│ ├── CloseTabHandler.php│ ├── MarkDrinksServedCommand.php│ ├── MarkDrinksServedHandler.php│ ├── MarkFoodServedCommand.php│ ├── MarkFoodServedHandler.php│ ├── OpenTabCommand.php│ ├── OpenTabHandler.php│ ├── PlaceOrderCommand.php│ ├── PlaceOrderHandler.php│ ├── PrepareFoodCommand.php│ └── PrepareFoodHandler.php├── DataTransformer│ ├── DataTranformer.php│ └── TabToArrayDataTransformer.php└── Query ├── AllTabsQuery.php ├── AllTabsQueryHandler.php ├── OneTabQuery.php └── OneTabQueryHandler.php

Page 128: CQRS + Event Sourcing in PHP

☁ events_cafe [master] tree -L 4 src/Infrastructuresrc/Infrastructure├── CommandBus│ └── CustomInflector.php├── Persistence│ ├── Domain│ │ └── Model│ │ ├── DoctrineOrderedItemRepository.php│ │ ├── InMemoryTabRepository.php│ │ └── TabEventSourcingRepository.php│ ├── EventStore│ │ ├── EventStore.php│ │ ├── PDOEventStore.php│ │ └── RedisEventStore.php│ └── Projection│ ├── BaseProjection.php│ ├── DrinksOrderedProjection.php│ ├── Projection.php│ ├── Projector.php│ ├── TabOpenedProjection.php│ └── TabProjection.php├── Serialize│ ├── JsonSerializer.php│ └── Serializer.php└── ui └── web └── app.php

Page 129: CQRS + Event Sourcing in PHP

☁ events_cafe [master] tree -L 2 src/Domainsrc/Domain├── Model│ ├── Aggregate│ ├── Events│ ├── OrderedItem│ └── Tab└── ReadModel ├── Items.php └── Tabs.php

Page 130: CQRS + Event Sourcing in PHP

☁ events_cafe [master] tree -L 2 src/Domain/Modelsrc/Domain/Model├── Aggregate│ ├── Aggregate.php│ └── AggregateId.php├── Events│ ├── DrinksOrdered.php│ ├── DrinksServed.php│ ├── FoodOrdered.php│ ├── FoodPrepared.php│ ├── FoodServed.php│ ├── TabClosed.php│ ├── TabEvent.php│ └── TabOpened.php├── OrderedItem│ ├── OrderedItem.php│ ├── OrderedItemNotExists.php│ └── OrderedItemsRepository.php└── Tab ├── DrinkIsNotOutstanding.php ├── FoodIsNotPrepared.php ├── FoodNotOutstanding.php ├── MustPayEnoughException.php ├── Tab.php ├── TabHasUnservedItems.php ├── TabId.php ├── TabNotExists.php ├── TabNotOpenException.php └── TabRepository.php

Page 131: CQRS + Event Sourcing in PHP

<?php

$app->post('/tab', function (Request $request) use ($app) {

// …

$command = new Command\OpenTabCommand(

\Ramsey\Uuid\Uuid::uuid4(),

$data['table'],

$data['waiter']

);

$app['command_bus']->handle($command);

// …

})

Page 132: CQRS + Event Sourcing in PHP

<?php

$app->get('/tab/{id}', function (Request $request, $id) use ($app)

{

$query = new Query\OneTabQuery($id);

$response = $app['query_bus']->handle($query);

return $app->json([

'tab' => $response

]);

});

Page 133: CQRS + Event Sourcing in PHP

Gracias y ..

Page 134: CQRS + Event Sourcing in PHP

Que la fuerza os acompañe.


Recommended