SlideShare a Scribd company logo
CQRS
Command Query Responsibility Segregation
Manel López Torrent
Ingeniero informática
Desarrollo software
Agile
@mloptor
malotor@gmail.com
EDUMENT
http://cqrs.nu
DDD in PHP
https://github.com/dddinphp/blog-cqrs
https://github.com/dddinphp/last-wishes-gamify
¿CQRS?
CQRS
Patrón de diseño de app
Lecturas / Escrituras
Modelo rico
Mejor rendimiento
Mejor escalabilidad
Objetivos
Greg Young
https://goodenoughsoftware.net/
@gregyoung
DDD (Domain Driven Design)
Modelo rico VS Modelo Anémico
Patrones tácticos
Arquitectura de capas
Arquitectura hexagonal
Requisitos
Cafetería
- 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
Abrir
Ordenar
Preparar
Servir
Pagar
Cerrar
Camarero
Mesa
Cuenta
Bebida / Comida
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
Application Layer
Domain
Model
Application
Service 1
Request
Infrastructure
Layer DataStoreApplication
Service 2
Application
Service n
Response
<?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);
}
<?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;
}
OK
Nuevos requisitos
Maître
Barman
Cocina
<?php
interface TabRepository
{
public function getById(TabId $tabId);
public function save(Tab $tab);
}
<?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);
}
<?php
use DoctrineORMQuery
interface TabRepository
{
public function getById(TabId $tabId);
public function save(Tab $tab);
public function getTabsByQuery(Query $query)
}
<?php
interface TabRepository
{
public function getById(TabId $tabId);
public function save(Tab $tab);
public function getTabsBySpeficication(Specification $s);
}
<?php
class MysqlTabRepository implements TabRepository { ... }
class RedisTabRepository implements TabRepository { ... }
class MongoDBTabRepository implements TabRepository { ... }
¿?
ORM
Frameworks
Contaminamos el modelo
SRP
SRP
Command Query
Responsibility
Segregation
Read Model Write Model
Read Model Write Model
Lógica de
negocio
Read Model Write Model
Query Command
Commands
OpenTab
PlaceOrder
MarkDrinksServed
MarkFoodPrepared
MarkFoodServed
CloseTab
Querys
AllTabs
OneTab
AllTabsByWaiter
Infrastructure
Layer
Application
Layer
Command
HandlerCommand
Command
Handler
Query
Handle
Read Model
Model
Command
Query
DataStore
Response
Query
Handle
Query
Response
Controladores Comando Bus Command Handler
Command
CommandHandler
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() { }
}
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);
}
}
Query
QueryHandler
class OneTabQuery
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
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();
}
}
Modelo Escritura
Tab
TabRepository
OrderedItem
OrderedItemRepository
Modelo Lectura
TabView
TabViewRepository
OrderedItemView
OrderedItemViewRepository
Modelo Escritura
Tab
TabRepository
OrderedItem
OrderedItemRepository
Modelo Lectura
TabView
TabViewRepository
OrderedItemView
OrderedItemViewRepository
Lógica negocio
Modelo Anémico
<?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);
}
Modelo rico
Mejor rendimiento
Mejor escalabilidad
¿Mejor rendimiento?
Infrastructure
Layer
Application
Layer
Command
HandlerCommand
Command
Handler
Query
Handle
Read Model
Model
Command
Query
DataStore
Response
Query
Handle
Query
Response
Model Infrastructure
Layer
DataStore
Infrastructure
Layer
DataStore
Application
Layer
Command
HandlerCommand
Command
Handler
Query
Handle
Read Model
Command
Query
Response
Query
Handle
Query
Response
<?php
class RedisTabRepository implements TabRepository { ... }
class MysqlTabViewRepository implements TabViewRepository { ... }
Modelo rico
Mejor rendimiento
Mejor escalabilidad
¿Mejor escalabilidad?
Model Infrastructure
Layer
DataStore
Infrastructure
Layer
DataStore
Application
Layer
Command
HandlerCommand
Command
Handler
Query
Handle
Read Model
Command
Query
Response
Query
Handle
Query
Response
WRITE SERVICE
READ SERVICE
API
GATEWA
Y
READ
SERVICE
WRITE
SERVICE
READ
SERVICE
READ
SERVICE
Modelo rico
Mejor rendimiento
Mejor escalabilidad
¿Consistencia de los datos?
Infrastructure
Layer
Application Layer
Command
HandlerCommand
Command
Handler
Query
Handle
Read Model
Model
Command
Query
DataStore
Response
Query
Handle
Query
Response
Infrastructure
Layer
DataStore
Eventos del dominio
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
TabOpened
DrinksOrdered
FoodOrdered
DrinksServed
FoodPrepared
FoodServed
TabClosed
<?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;
}
}
Command Model
Evento
BUS
Model
Evento
Evento
Model Evento Listenner
BUS
DataStore
Model Listenner
BUS
Event sourcing
El estado de nuestro sistema
es la suma de todos los eventos
ocurridos en el.
A1 {
x = 1
y = 2
}
ID X Y
A1 1 2
A2 3 5 A2 {
x = 3
y = 5
}
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 = null
y = null
}
Tiempo
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 = null
y = null
}
A1 {
x = 1
y = null
}
Tiempo
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 = null
y = null
}
A1 {
x = 1
y = null
}
A2 {
x = null
y = null
}
Tiempo
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 = null
y = null
}
A1 {
x = 1
y = null
}
A1 {
x = 1
y = 7
}
A2 {
x = null
y = null
}
Tiempo
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 = null
y = null
}
A1 {
x = 1
y = null
}
A1 {
x = 1
y = 7
}
A2 {
x = null
y = null
}
A2 {
x = 3
y = null
}
Tiempo
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 = null
y = null
}
A1 {
x = 1
y = null
}
A1 {
x = 1
y = 7
}
A1 {
x = 1
y = 2
}
A2 {
x = null
y = null
}
A2 {
x = 3
y = null
}
Tiempo
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 = null
y = null
}
A1 {
x = 1
y = null
}
A1 {
x = 1
y = 7
}
A1 {
x = 1
y = 2
}
A2 {
x = null
y = null
}
A2 {
x = 3
y = null
}
A2 {
x = 3
y = 5
}
Tiempo
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 = null
y = null
}
A1 {
x = 1
y = null
}
A1 {
x = 1
y = 7
}
A1 {
x = 1
y = 2
}
A2 {
x = null
y = null
}
A2 {
x = 3
y = null
}
A2 {
x = 3
y = null
}
Historia
Auditoria
Fácil persistencia
Reconstruimos nuestros agregado a partir de un
flujo de eventos
Command Model
Evento
BUS
Model
Evento
Evento
EventStore
Proyecciones
ID X Y
A1 1 2
A2 3 5
A1, y = 2
A1 , y = 7
A1 , x = 1
A3, y = 5
A2, x = 3
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
ID X Y
A1 1 2
A2 3 5
UPDATE table_name
SET x=2 WHERE id =
A1A1, x = 2
A1, y = 2
A1 , y = 7
A1 , x = 1
A3, y = 5
A2, x = 3
ID X Y
A1 2 2
A2 3 5
A1, x = 2
A1, y = 2
A1 , y = 7
A1 , x = 1
Inconsistencia Eventual
¿Dónde generamos los eventos?
Agregados
Entidad raíz
Id del agregado
Almacenar los eventos
Reconstruir desde flujo de eventos
<?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) {}
}
<?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) {}
}
<?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;
}
}
<?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;
}
}
<?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;
}
}
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 = [];
}
}
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);
}
}
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;
});
}
}
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;
});
}
}
Refactorizar agregados
<?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;
});
}
}
<?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
));
}
}
<?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;
});
}
}
<?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
));
}
}
<?php
class Tab extends Aggregate {
public function serveDrinks($drinksServed)
{
$this->assertDrinksAreOutstanding($drinksServed);
$drinksServedEvend = new DrinksServed(
$this->getAggregateId(),
$drinksServed
);
$this->recordThat($drinksServedEvend);
$this->apply($drinksServedEvend);
}
}
class Tab extends Aggregate {
public function serveDrinks($drinksServed)
{
$this->assertDrinksAreOutstanding($drinksServed);
$this->applyAndRecordThat(new DrinksServed(
$this->getAggregateId(),
$drinksServed
));
}
}
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
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
2 - Lo Aplicamos y lo guardamos
Repositorio
Recuperar flujo de eventos
Persistir eventos guardados
Publicar el flujo eventos
<?php
interface AggregateRepository
{
public function get(IdentifiesAggregate $aggregateId):
AggregateRoot;
public function add(RecordsEvents $aggregate);
}
<?php
class TabEventSourcingRepository implements TabRepository
{
private $eventStore;
private $projector;
public function __construct(
EventStore $eventStore,
$projector
) {
$this->eventStore = $eventStore;
$this->projector = $projector;
}
}
Event Store
<?php
interface EventStore
{
public function commit(DomainEvents $events);
public function getAggregateHistoryFor(IdentifiesAggregate $id);
}
Serializar
{
"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
Proyecciones
<?php
interface Projection
{
public function eventType();
public function project($event);
}
<?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;
}
}
<?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);
}
}
}
Modelo lectura
Modelo anémico
DTO
Entidades generadas ORM
Repositorios generados ORM
Frameworks
There are no dumb questions ...
https://github.com/malotor/cafe_events
PHP 7.1
Phpunit 6
Docker
☁ 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
☁ events_cafe [master] tree -L 2 src
src
├── Application
│ ├── Command
│ ├── DataTransformer
│ └── Query
├── Domain
│ ├── Model
│ └── ReadModel
└── Infrastructure
├── CommandBus
├── Persistence
├── Serialize
└── ui
☁ events_cafe [master] tree -L 2 src/Application
src/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
☁ events_cafe [master] tree -L 4 src/Infrastructure
src/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
☁ events_cafe [master] tree -L 2 src/Domain
src/Domain
├── Model
│ ├── Aggregate
│ ├── Events
│ ├── OrderedItem
│ └── Tab
└── ReadModel
├── Items.php
└── Tabs.php
☁ events_cafe [master] tree -L 2 src/Domain/Model
src/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
<?php
$app->post('/tab', function (Request $request) use ($app) {
// …
$command = new CommandOpenTabCommand(
RamseyUuidUuid::uuid4(),
$data['table'],
$data['waiter']
);
$app['command_bus']->handle($command);
// …
})
<?php
$app->get('/tab/{id}', function (Request $request, $id) use ($app)
{
$query = new QueryOneTabQuery($id);
$response = $app['query_bus']->handle($query);
return $app->json([
'tab' => $response
]);
});
Gracias y ..
Que la fuerza os acompañe.

More Related Content

What's hot

Hexagonal architecture: how, why and when
Hexagonal architecture: how, why and whenHexagonal architecture: how, why and when
Hexagonal architecture: how, why and when
Xoubaman
 
Workshop 4: NodeJS. Express Framework & MongoDB.
Workshop 4: NodeJS. Express Framework & MongoDB.Workshop 4: NodeJS. Express Framework & MongoDB.
Workshop 4: NodeJS. Express Framework & MongoDB.
Visual Engineering
 
Introduction to VueJS & Vuex
Introduction to VueJS & VuexIntroduction to VueJS & Vuex
Introduction to VueJS & Vuex
Bernd Alter
 
Implementing DDD with C#
Implementing DDD with C#Implementing DDD with C#
Implementing DDD with C#
Pascal Laurin
 
What is Node.js | Node.js Tutorial for Beginners | Node.js Modules | Node.js ...
What is Node.js | Node.js Tutorial for Beginners | Node.js Modules | Node.js ...What is Node.js | Node.js Tutorial for Beginners | Node.js Modules | Node.js ...
What is Node.js | Node.js Tutorial for Beginners | Node.js Modules | Node.js ...
Edureka!
 
Anatomy of a Spring Boot App with Clean Architecture - Spring I/O 2023
Anatomy of a Spring Boot App with Clean Architecture - Spring I/O 2023Anatomy of a Spring Boot App with Clean Architecture - Spring I/O 2023
Anatomy of a Spring Boot App with Clean Architecture - Spring I/O 2023
Steve Pember
 
WEB DEVELOPMENT USING REACT JS
 WEB DEVELOPMENT USING REACT JS WEB DEVELOPMENT USING REACT JS
WEB DEVELOPMENT USING REACT JS
MuthuKumaran Singaravelu
 
Kata: Hexagonal Architecture / Ports and Adapters
Kata: Hexagonal Architecture / Ports and AdaptersKata: Hexagonal Architecture / Ports and Adapters
Kata: Hexagonal Architecture / Ports and Adapters
holsky
 
Design functional solutions in Java, a practical example
Design functional solutions in Java, a practical exampleDesign functional solutions in Java, a practical example
Design functional solutions in Java, a practical example
Marian Wamsiedel
 
Domain Driven Design Demonstrated
Domain Driven Design Demonstrated Domain Driven Design Demonstrated
Domain Driven Design Demonstrated
Alan Christensen
 
Introduction to Spring Cloud
Introduction to Spring Cloud           Introduction to Spring Cloud
Introduction to Spring Cloud
VMware Tanzu
 
Node js
Node jsNode js
Introduction to React JS
Introduction to React JSIntroduction to React JS
Introduction to React JS
Bethmi Gunasekara
 
Clean Code
Clean CodeClean Code
Clean Code
Luigi De Russis
 
ASP.NET Core MVC + Web API with Overview
ASP.NET Core MVC + Web API with OverviewASP.NET Core MVC + Web API with Overview
ASP.NET Core MVC + Web API with Overview
Shahed Chowdhuri
 
Introduction to ReactJS
Introduction to ReactJSIntroduction to ReactJS
Introduction to ReactJS
Hoang Long
 
Java Concurrency Gotchas
Java Concurrency GotchasJava Concurrency Gotchas
Java Concurrency Gotchas
Alex Miller
 
Introduction to React JS for beginners
Introduction to React JS for beginners Introduction to React JS for beginners
Introduction to React JS for beginners
Varun Raj
 
The Secrets of Hexagonal Architecture
The Secrets of Hexagonal ArchitectureThe Secrets of Hexagonal Architecture
The Secrets of Hexagonal Architecture
Nicolas Carlo
 
Hexagonal Architecture
Hexagonal ArchitectureHexagonal Architecture
Hexagonal Architecture
Marcelo Cure
 

What's hot (20)

Hexagonal architecture: how, why and when
Hexagonal architecture: how, why and whenHexagonal architecture: how, why and when
Hexagonal architecture: how, why and when
 
Workshop 4: NodeJS. Express Framework & MongoDB.
Workshop 4: NodeJS. Express Framework & MongoDB.Workshop 4: NodeJS. Express Framework & MongoDB.
Workshop 4: NodeJS. Express Framework & MongoDB.
 
Introduction to VueJS & Vuex
Introduction to VueJS & VuexIntroduction to VueJS & Vuex
Introduction to VueJS & Vuex
 
Implementing DDD with C#
Implementing DDD with C#Implementing DDD with C#
Implementing DDD with C#
 
What is Node.js | Node.js Tutorial for Beginners | Node.js Modules | Node.js ...
What is Node.js | Node.js Tutorial for Beginners | Node.js Modules | Node.js ...What is Node.js | Node.js Tutorial for Beginners | Node.js Modules | Node.js ...
What is Node.js | Node.js Tutorial for Beginners | Node.js Modules | Node.js ...
 
Anatomy of a Spring Boot App with Clean Architecture - Spring I/O 2023
Anatomy of a Spring Boot App with Clean Architecture - Spring I/O 2023Anatomy of a Spring Boot App with Clean Architecture - Spring I/O 2023
Anatomy of a Spring Boot App with Clean Architecture - Spring I/O 2023
 
WEB DEVELOPMENT USING REACT JS
 WEB DEVELOPMENT USING REACT JS WEB DEVELOPMENT USING REACT JS
WEB DEVELOPMENT USING REACT JS
 
Kata: Hexagonal Architecture / Ports and Adapters
Kata: Hexagonal Architecture / Ports and AdaptersKata: Hexagonal Architecture / Ports and Adapters
Kata: Hexagonal Architecture / Ports and Adapters
 
Design functional solutions in Java, a practical example
Design functional solutions in Java, a practical exampleDesign functional solutions in Java, a practical example
Design functional solutions in Java, a practical example
 
Domain Driven Design Demonstrated
Domain Driven Design Demonstrated Domain Driven Design Demonstrated
Domain Driven Design Demonstrated
 
Introduction to Spring Cloud
Introduction to Spring Cloud           Introduction to Spring Cloud
Introduction to Spring Cloud
 
Node js
Node jsNode js
Node js
 
Introduction to React JS
Introduction to React JSIntroduction to React JS
Introduction to React JS
 
Clean Code
Clean CodeClean Code
Clean Code
 
ASP.NET Core MVC + Web API with Overview
ASP.NET Core MVC + Web API with OverviewASP.NET Core MVC + Web API with Overview
ASP.NET Core MVC + Web API with Overview
 
Introduction to ReactJS
Introduction to ReactJSIntroduction to ReactJS
Introduction to ReactJS
 
Java Concurrency Gotchas
Java Concurrency GotchasJava Concurrency Gotchas
Java Concurrency Gotchas
 
Introduction to React JS for beginners
Introduction to React JS for beginners Introduction to React JS for beginners
Introduction to React JS for beginners
 
The Secrets of Hexagonal Architecture
The Secrets of Hexagonal ArchitectureThe Secrets of Hexagonal Architecture
The Secrets of Hexagonal Architecture
 
Hexagonal Architecture
Hexagonal ArchitectureHexagonal Architecture
Hexagonal Architecture
 

Similar to CQRS + Event Sourcing in PHP

CQRS + ES. Más allá del hexágono
CQRS + ES. Más allá del hexágonoCQRS + ES. Más allá del hexágono
CQRS + ES. Más allá del hexágono
Manuel López Torrent
 
Tips on how to improve the performance of your custom modules for high volume...
Tips on how to improve the performance of your custom modules for high volume...Tips on how to improve the performance of your custom modules for high volume...
Tips on how to improve the performance of your custom modules for high volume...
Odoo
 
R Programming: Mathematical Functions In R
R Programming: Mathematical Functions In RR Programming: Mathematical Functions In R
R Programming: Mathematical Functions In R
Rsquared Academy
 
Reactive Programming - ReactFoo 2020 - Aziz Khambati
Reactive Programming - ReactFoo 2020 - Aziz KhambatiReactive Programming - ReactFoo 2020 - Aziz Khambati
Reactive Programming - ReactFoo 2020 - Aziz Khambati
Aziz Khambati
 
Compose Async with RxJS
Compose Async with RxJSCompose Async with RxJS
Compose Async with RxJS
Kyung Yeol Kim
 
Hybrid rule engines (rulesfest 2010)
Hybrid rule engines (rulesfest 2010)Hybrid rule engines (rulesfest 2010)
Hybrid rule engines (rulesfest 2010)
Geoffrey De Smet
 
The Ring programming language version 1.6 book - Part 9 of 189
The Ring programming language version 1.6 book - Part 9 of 189The Ring programming language version 1.6 book - Part 9 of 189
The Ring programming language version 1.6 book - Part 9 of 189
Mahmoud Samir Fayed
 
The Ring programming language version 1.7 book - Part 10 of 196
The Ring programming language version 1.7 book - Part 10 of 196The Ring programming language version 1.7 book - Part 10 of 196
The Ring programming language version 1.7 book - Part 10 of 196
Mahmoud Samir Fayed
 
Unit test candidate solutions
Unit test candidate solutionsUnit test candidate solutions
Unit test candidate solutions
benewu
 
00_Introduction to Java.ppt
00_Introduction to Java.ppt00_Introduction to Java.ppt
00_Introduction to Java.ppt
HongAnhNguyn285885
 
Sparkling Water Meetup
Sparkling Water MeetupSparkling Water Meetup
Sparkling Water Meetup
Sri Ambati
 
Causal Inference in R
Causal Inference in RCausal Inference in R
Causal Inference in R
Ana Daglis
 
COCOA: Communication-Efficient Coordinate Ascent
COCOA: Communication-Efficient Coordinate AscentCOCOA: Communication-Efficient Coordinate Ascent
COCOA: Communication-Efficient Coordinate Ascent
jeykottalam
 
Clean code & design patterns
Clean code & design patternsClean code & design patterns
Clean code & design patterns
Pascal Larocque
 
CS101- Introduction to Computing- Lecture 35
CS101- Introduction to Computing- Lecture 35CS101- Introduction to Computing- Lecture 35
CS101- Introduction to Computing- Lecture 35
Bilal Ahmed
 
Laravel tips-2019-04
Laravel tips-2019-04Laravel tips-2019-04
Laravel tips-2019-04
Fernando Andrés Pérez Alarcón
 
Performance measurement and tuning
Performance measurement and tuningPerformance measurement and tuning
Performance measurement and tuning
AOE
 
R Programming: Comparing Objects In R
R Programming: Comparing Objects In RR Programming: Comparing Objects In R
R Programming: Comparing Objects In R
Rsquared Academy
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with Groovy
Arturo Herrero
 
NYC* 2013 - "Advanced Data Processing: Beyond Queries and Slices"
NYC* 2013 - "Advanced Data Processing: Beyond Queries and Slices"NYC* 2013 - "Advanced Data Processing: Beyond Queries and Slices"
NYC* 2013 - "Advanced Data Processing: Beyond Queries and Slices"
DataStax Academy
 

Similar to CQRS + Event Sourcing in PHP (20)

CQRS + ES. Más allá del hexágono
CQRS + ES. Más allá del hexágonoCQRS + ES. Más allá del hexágono
CQRS + ES. Más allá del hexágono
 
Tips on how to improve the performance of your custom modules for high volume...
Tips on how to improve the performance of your custom modules for high volume...Tips on how to improve the performance of your custom modules for high volume...
Tips on how to improve the performance of your custom modules for high volume...
 
R Programming: Mathematical Functions In R
R Programming: Mathematical Functions In RR Programming: Mathematical Functions In R
R Programming: Mathematical Functions In R
 
Reactive Programming - ReactFoo 2020 - Aziz Khambati
Reactive Programming - ReactFoo 2020 - Aziz KhambatiReactive Programming - ReactFoo 2020 - Aziz Khambati
Reactive Programming - ReactFoo 2020 - Aziz Khambati
 
Compose Async with RxJS
Compose Async with RxJSCompose Async with RxJS
Compose Async with RxJS
 
Hybrid rule engines (rulesfest 2010)
Hybrid rule engines (rulesfest 2010)Hybrid rule engines (rulesfest 2010)
Hybrid rule engines (rulesfest 2010)
 
The Ring programming language version 1.6 book - Part 9 of 189
The Ring programming language version 1.6 book - Part 9 of 189The Ring programming language version 1.6 book - Part 9 of 189
The Ring programming language version 1.6 book - Part 9 of 189
 
The Ring programming language version 1.7 book - Part 10 of 196
The Ring programming language version 1.7 book - Part 10 of 196The Ring programming language version 1.7 book - Part 10 of 196
The Ring programming language version 1.7 book - Part 10 of 196
 
Unit test candidate solutions
Unit test candidate solutionsUnit test candidate solutions
Unit test candidate solutions
 
00_Introduction to Java.ppt
00_Introduction to Java.ppt00_Introduction to Java.ppt
00_Introduction to Java.ppt
 
Sparkling Water Meetup
Sparkling Water MeetupSparkling Water Meetup
Sparkling Water Meetup
 
Causal Inference in R
Causal Inference in RCausal Inference in R
Causal Inference in R
 
COCOA: Communication-Efficient Coordinate Ascent
COCOA: Communication-Efficient Coordinate AscentCOCOA: Communication-Efficient Coordinate Ascent
COCOA: Communication-Efficient Coordinate Ascent
 
Clean code & design patterns
Clean code & design patternsClean code & design patterns
Clean code & design patterns
 
CS101- Introduction to Computing- Lecture 35
CS101- Introduction to Computing- Lecture 35CS101- Introduction to Computing- Lecture 35
CS101- Introduction to Computing- Lecture 35
 
Laravel tips-2019-04
Laravel tips-2019-04Laravel tips-2019-04
Laravel tips-2019-04
 
Performance measurement and tuning
Performance measurement and tuningPerformance measurement and tuning
Performance measurement and tuning
 
R Programming: Comparing Objects In R
R Programming: Comparing Objects In RR Programming: Comparing Objects In R
R Programming: Comparing Objects In R
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with Groovy
 
NYC* 2013 - "Advanced Data Processing: Beyond Queries and Slices"
NYC* 2013 - "Advanced Data Processing: Beyond Queries and Slices"NYC* 2013 - "Advanced Data Processing: Beyond Queries and Slices"
NYC* 2013 - "Advanced Data Processing: Beyond Queries and Slices"
 

Recently uploaded

办理新西兰奥克兰大学毕业证学位证书范本原版一模一样
办理新西兰奥克兰大学毕业证学位证书范本原版一模一样办理新西兰奥克兰大学毕业证学位证书范本原版一模一样
办理新西兰奥克兰大学毕业证学位证书范本原版一模一样
xjq03c34
 
快速办理(新加坡SMU毕业证书)新加坡管理大学毕业证文凭证书一模一样
快速办理(新加坡SMU毕业证书)新加坡管理大学毕业证文凭证书一模一样快速办理(新加坡SMU毕业证书)新加坡管理大学毕业证文凭证书一模一样
快速办理(新加坡SMU毕业证书)新加坡管理大学毕业证文凭证书一模一样
3a0sd7z3
 
Bengaluru Dreamin' 24 - Personal Branding
Bengaluru Dreamin' 24 - Personal BrandingBengaluru Dreamin' 24 - Personal Branding
Bengaluru Dreamin' 24 - Personal Branding
Tarandeep Singh
 
Securing BGP: Operational Strategies and Best Practices for Network Defenders...
Securing BGP: Operational Strategies and Best Practices for Network Defenders...Securing BGP: Operational Strategies and Best Practices for Network Defenders...
Securing BGP: Operational Strategies and Best Practices for Network Defenders...
APNIC
 
Integrating Physical and Cybersecurity to Lower Risks in Healthcare!
Integrating Physical and Cybersecurity to Lower Risks in Healthcare!Integrating Physical and Cybersecurity to Lower Risks in Healthcare!
Integrating Physical and Cybersecurity to Lower Risks in Healthcare!
Alec Kassir cozmozone
 
一比一原版(USYD毕业证)悉尼大学毕业证如何办理
一比一原版(USYD毕业证)悉尼大学毕业证如何办理一比一原版(USYD毕业证)悉尼大学毕业证如何办理
一比一原版(USYD毕业证)悉尼大学毕业证如何办理
k4ncd0z
 
一比一原版新西兰林肯大学毕业证(Lincoln毕业证书)学历如何办理
一比一原版新西兰林肯大学毕业证(Lincoln毕业证书)学历如何办理一比一原版新西兰林肯大学毕业证(Lincoln毕业证书)学历如何办理
一比一原版新西兰林肯大学毕业证(Lincoln毕业证书)学历如何办理
thezot
 
怎么办理(umiami毕业证书)美国迈阿密大学毕业证文凭证书实拍图原版一模一样
怎么办理(umiami毕业证书)美国迈阿密大学毕业证文凭证书实拍图原版一模一样怎么办理(umiami毕业证书)美国迈阿密大学毕业证文凭证书实拍图原版一模一样
怎么办理(umiami毕业证书)美国迈阿密大学毕业证文凭证书实拍图原版一模一样
rtunex8r
 
Discover the benefits of outsourcing SEO to India
Discover the benefits of outsourcing SEO to IndiaDiscover the benefits of outsourcing SEO to India
Discover the benefits of outsourcing SEO to India
davidjhones387
 
HijackLoader Evolution: Interactive Process Hollowing
HijackLoader Evolution: Interactive Process HollowingHijackLoader Evolution: Interactive Process Hollowing
HijackLoader Evolution: Interactive Process Hollowing
Donato Onofri
 
Honeypots Unveiled: Proactive Defense Tactics for Cyber Security, Phoenix Sum...
Honeypots Unveiled: Proactive Defense Tactics for Cyber Security, Phoenix Sum...Honeypots Unveiled: Proactive Defense Tactics for Cyber Security, Phoenix Sum...
Honeypots Unveiled: Proactive Defense Tactics for Cyber Security, Phoenix Sum...
APNIC
 
How to make a complaint to the police for Social Media Fraud.pdf
How to make a complaint to the police for Social Media Fraud.pdfHow to make a complaint to the police for Social Media Fraud.pdf
How to make a complaint to the police for Social Media Fraud.pdf
Infosec train
 
快速办理(Vic毕业证书)惠灵顿维多利亚大学毕业证完成信一模一样
快速办理(Vic毕业证书)惠灵顿维多利亚大学毕业证完成信一模一样快速办理(Vic毕业证书)惠灵顿维多利亚大学毕业证完成信一模一样
快速办理(Vic毕业证书)惠灵顿维多利亚大学毕业证完成信一模一样
3a0sd7z3
 
Should Repositories Participate in the Fediverse?
Should Repositories Participate in the Fediverse?Should Repositories Participate in the Fediverse?
Should Repositories Participate in the Fediverse?
Paul Walk
 

Recently uploaded (14)

办理新西兰奥克兰大学毕业证学位证书范本原版一模一样
办理新西兰奥克兰大学毕业证学位证书范本原版一模一样办理新西兰奥克兰大学毕业证学位证书范本原版一模一样
办理新西兰奥克兰大学毕业证学位证书范本原版一模一样
 
快速办理(新加坡SMU毕业证书)新加坡管理大学毕业证文凭证书一模一样
快速办理(新加坡SMU毕业证书)新加坡管理大学毕业证文凭证书一模一样快速办理(新加坡SMU毕业证书)新加坡管理大学毕业证文凭证书一模一样
快速办理(新加坡SMU毕业证书)新加坡管理大学毕业证文凭证书一模一样
 
Bengaluru Dreamin' 24 - Personal Branding
Bengaluru Dreamin' 24 - Personal BrandingBengaluru Dreamin' 24 - Personal Branding
Bengaluru Dreamin' 24 - Personal Branding
 
Securing BGP: Operational Strategies and Best Practices for Network Defenders...
Securing BGP: Operational Strategies and Best Practices for Network Defenders...Securing BGP: Operational Strategies and Best Practices for Network Defenders...
Securing BGP: Operational Strategies and Best Practices for Network Defenders...
 
Integrating Physical and Cybersecurity to Lower Risks in Healthcare!
Integrating Physical and Cybersecurity to Lower Risks in Healthcare!Integrating Physical and Cybersecurity to Lower Risks in Healthcare!
Integrating Physical and Cybersecurity to Lower Risks in Healthcare!
 
一比一原版(USYD毕业证)悉尼大学毕业证如何办理
一比一原版(USYD毕业证)悉尼大学毕业证如何办理一比一原版(USYD毕业证)悉尼大学毕业证如何办理
一比一原版(USYD毕业证)悉尼大学毕业证如何办理
 
一比一原版新西兰林肯大学毕业证(Lincoln毕业证书)学历如何办理
一比一原版新西兰林肯大学毕业证(Lincoln毕业证书)学历如何办理一比一原版新西兰林肯大学毕业证(Lincoln毕业证书)学历如何办理
一比一原版新西兰林肯大学毕业证(Lincoln毕业证书)学历如何办理
 
怎么办理(umiami毕业证书)美国迈阿密大学毕业证文凭证书实拍图原版一模一样
怎么办理(umiami毕业证书)美国迈阿密大学毕业证文凭证书实拍图原版一模一样怎么办理(umiami毕业证书)美国迈阿密大学毕业证文凭证书实拍图原版一模一样
怎么办理(umiami毕业证书)美国迈阿密大学毕业证文凭证书实拍图原版一模一样
 
Discover the benefits of outsourcing SEO to India
Discover the benefits of outsourcing SEO to IndiaDiscover the benefits of outsourcing SEO to India
Discover the benefits of outsourcing SEO to India
 
HijackLoader Evolution: Interactive Process Hollowing
HijackLoader Evolution: Interactive Process HollowingHijackLoader Evolution: Interactive Process Hollowing
HijackLoader Evolution: Interactive Process Hollowing
 
Honeypots Unveiled: Proactive Defense Tactics for Cyber Security, Phoenix Sum...
Honeypots Unveiled: Proactive Defense Tactics for Cyber Security, Phoenix Sum...Honeypots Unveiled: Proactive Defense Tactics for Cyber Security, Phoenix Sum...
Honeypots Unveiled: Proactive Defense Tactics for Cyber Security, Phoenix Sum...
 
How to make a complaint to the police for Social Media Fraud.pdf
How to make a complaint to the police for Social Media Fraud.pdfHow to make a complaint to the police for Social Media Fraud.pdf
How to make a complaint to the police for Social Media Fraud.pdf
 
快速办理(Vic毕业证书)惠灵顿维多利亚大学毕业证完成信一模一样
快速办理(Vic毕业证书)惠灵顿维多利亚大学毕业证完成信一模一样快速办理(Vic毕业证书)惠灵顿维多利亚大学毕业证完成信一模一样
快速办理(Vic毕业证书)惠灵顿维多利亚大学毕业证完成信一模一样
 
Should Repositories Participate in the Fediverse?
Should Repositories Participate in the Fediverse?Should Repositories Participate in the Fediverse?
Should Repositories Participate in the Fediverse?
 

CQRS + Event Sourcing in PHP