SlideShare una empresa de Scribd logo
1 de 134
Descargar para leer sin conexión
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.

Más contenido relacionado

La actualidad más candente

MongoDB Aggregation Framework
MongoDB Aggregation FrameworkMongoDB Aggregation Framework
MongoDB Aggregation FrameworkCaserta
 
Kubernetes Forum Seoul 2019: Re-architecting Data Platform with Kubernetes
Kubernetes Forum Seoul 2019: Re-architecting Data Platform with KubernetesKubernetes Forum Seoul 2019: Re-architecting Data Platform with Kubernetes
Kubernetes Forum Seoul 2019: Re-architecting Data Platform with KubernetesSeungYong Oh
 
Angular - Chapter 4 - Data and Event Handling
 Angular - Chapter 4 - Data and Event Handling Angular - Chapter 4 - Data and Event Handling
Angular - Chapter 4 - Data and Event HandlingWebStackAcademy
 
Introduction to VueJS & Vuex
Introduction to VueJS & VuexIntroduction to VueJS & Vuex
Introduction to VueJS & VuexBernd Alter
 
Build automated Machine Images using Packer
Build automated Machine Images using PackerBuild automated Machine Images using Packer
Build automated Machine Images using PackerMarek Piątek
 
Nest.js Introduction
Nest.js IntroductionNest.js Introduction
Nest.js IntroductionTakuya Tejima
 
Advanced Javascript
Advanced JavascriptAdvanced Javascript
Advanced JavascriptAdieu
 
왜 쿠버네티스는 systemd로 cgroup을 관리하려고 할까요
왜 쿠버네티스는 systemd로 cgroup을 관리하려고 할까요왜 쿠버네티스는 systemd로 cgroup을 관리하려고 할까요
왜 쿠버네티스는 systemd로 cgroup을 관리하려고 할까요Jo Hoon
 
이벤트 기반 분산 시스템을 향한 여정
이벤트 기반 분산 시스템을 향한 여정이벤트 기반 분산 시스템을 향한 여정
이벤트 기반 분산 시스템을 향한 여정Arawn Park
 
Sharing Data Between Angular Components
Sharing Data Between Angular ComponentsSharing Data Between Angular Components
Sharing Data Between Angular ComponentsSquash Apps Pvt Ltd
 
ES6 presentation
ES6 presentationES6 presentation
ES6 presentationritika1
 
Consuming Restful APIs using Swagger v2.0
Consuming Restful APIs using Swagger v2.0Consuming Restful APIs using Swagger v2.0
Consuming Restful APIs using Swagger v2.0Pece Nikolovski
 
[야생의 땅: 듀랑고] 지형 관리 완전 자동화 - 생생한 AWS와 Docker 체험기
[야생의 땅: 듀랑고] 지형 관리 완전 자동화 - 생생한 AWS와 Docker 체험기[야생의 땅: 듀랑고] 지형 관리 완전 자동화 - 생생한 AWS와 Docker 체험기
[야생의 땅: 듀랑고] 지형 관리 완전 자동화 - 생생한 AWS와 Docker 체험기Sumin Byeon
 

La actualidad más candente (20)

NestJS
NestJSNestJS
NestJS
 
RESTful API - Best Practices
RESTful API - Best PracticesRESTful API - Best Practices
RESTful API - Best Practices
 
MongoDB Aggregation Framework
MongoDB Aggregation FrameworkMongoDB Aggregation Framework
MongoDB Aggregation Framework
 
Kubernetes Forum Seoul 2019: Re-architecting Data Platform with Kubernetes
Kubernetes Forum Seoul 2019: Re-architecting Data Platform with KubernetesKubernetes Forum Seoul 2019: Re-architecting Data Platform with Kubernetes
Kubernetes Forum Seoul 2019: Re-architecting Data Platform with Kubernetes
 
Angular - Chapter 4 - Data and Event Handling
 Angular - Chapter 4 - Data and Event Handling Angular - Chapter 4 - Data and Event Handling
Angular - Chapter 4 - Data and Event Handling
 
Introduction to VueJS & Vuex
Introduction to VueJS & VuexIntroduction to VueJS & Vuex
Introduction to VueJS & Vuex
 
Build automated Machine Images using Packer
Build automated Machine Images using PackerBuild automated Machine Images using Packer
Build automated Machine Images using Packer
 
Nest.js Introduction
Nest.js IntroductionNest.js Introduction
Nest.js Introduction
 
Advanced Javascript
Advanced JavascriptAdvanced Javascript
Advanced Javascript
 
Angular
AngularAngular
Angular
 
Packer by HashiCorp
Packer by HashiCorpPacker by HashiCorp
Packer by HashiCorp
 
왜 쿠버네티스는 systemd로 cgroup을 관리하려고 할까요
왜 쿠버네티스는 systemd로 cgroup을 관리하려고 할까요왜 쿠버네티스는 systemd로 cgroup을 관리하려고 할까요
왜 쿠버네티스는 systemd로 cgroup을 관리하려고 할까요
 
Javascript by geetanjali
Javascript by geetanjaliJavascript by geetanjali
Javascript by geetanjali
 
이벤트 기반 분산 시스템을 향한 여정
이벤트 기반 분산 시스템을 향한 여정이벤트 기반 분산 시스템을 향한 여정
이벤트 기반 분산 시스템을 향한 여정
 
Sharing Data Between Angular Components
Sharing Data Between Angular ComponentsSharing Data Between Angular Components
Sharing Data Between Angular Components
 
Packer
Packer Packer
Packer
 
ES6 presentation
ES6 presentationES6 presentation
ES6 presentation
 
Consuming Restful APIs using Swagger v2.0
Consuming Restful APIs using Swagger v2.0Consuming Restful APIs using Swagger v2.0
Consuming Restful APIs using Swagger v2.0
 
AngularJS
AngularJS AngularJS
AngularJS
 
[야생의 땅: 듀랑고] 지형 관리 완전 자동화 - 생생한 AWS와 Docker 체험기
[야생의 땅: 듀랑고] 지형 관리 완전 자동화 - 생생한 AWS와 Docker 체험기[야생의 땅: 듀랑고] 지형 관리 완전 자동화 - 생생한 AWS와 Docker 체험기
[야생의 땅: 듀랑고] 지형 관리 완전 자동화 - 생생한 AWS와 Docker 체험기
 

Similar a CQRS + Event Sourcing in PHP

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 RRsquared Academy
 
Reactive Programming - ReactFoo 2020 - Aziz Khambati
Reactive Programming - ReactFoo 2020 - Aziz KhambatiReactive Programming - ReactFoo 2020 - Aziz Khambati
Reactive Programming - ReactFoo 2020 - Aziz KhambatiAziz Khambati
 
Compose Async with RxJS
Compose Async with RxJSCompose Async with RxJS
Compose Async with RxJSKyung 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 189Mahmoud 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 196Mahmoud Samir Fayed
 
Unit test candidate solutions
Unit test candidate solutionsUnit test candidate solutions
Unit test candidate solutionsbenewu
 
Sparkling Water Meetup
Sparkling Water MeetupSparkling Water Meetup
Sparkling Water MeetupSri Ambati
 
Causal Inference in R
Causal Inference in RCausal Inference in R
Causal Inference in RAna Daglis
 
COCOA: Communication-Efficient Coordinate Ascent
COCOA: Communication-Efficient Coordinate AscentCOCOA: Communication-Efficient Coordinate Ascent
COCOA: Communication-Efficient Coordinate Ascentjeykottalam
 
Clean code & design patterns
Clean code & design patternsClean code & design patterns
Clean code & design patternsPascal Larocque
 
CS101- Introduction to Computing- Lecture 35
CS101- Introduction to Computing- Lecture 35CS101- Introduction to Computing- Lecture 35
CS101- Introduction to Computing- Lecture 35Bilal Ahmed
 
Performance measurement and tuning
Performance measurement and tuningPerformance measurement and tuning
Performance measurement and tuningAOE
 
R Programming: Comparing Objects In R
R Programming: Comparing Objects In RR Programming: Comparing Objects In R
R Programming: Comparing Objects In RRsquared Academy
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with GroovyArturo Herrero
 
Intravert Server side processing for Cassandra
Intravert Server side processing for CassandraIntravert Server side processing for Cassandra
Intravert Server side processing for CassandraEdward Capriolo
 

Similar a 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
 
Intravert Server side processing for Cassandra
Intravert Server side processing for CassandraIntravert Server side processing for Cassandra
Intravert Server side processing for Cassandra
 

Último

Nagercoil Escorts Service Girl ^ 9332606886, WhatsApp Anytime Nagercoil
Nagercoil Escorts Service Girl ^ 9332606886, WhatsApp Anytime NagercoilNagercoil Escorts Service Girl ^ 9332606886, WhatsApp Anytime Nagercoil
Nagercoil Escorts Service Girl ^ 9332606886, WhatsApp Anytime Nagercoilmeghakumariji156
 
20240509 QFM015 Engineering Leadership Reading List April 2024.pdf
20240509 QFM015 Engineering Leadership Reading List April 2024.pdf20240509 QFM015 Engineering Leadership Reading List April 2024.pdf
20240509 QFM015 Engineering Leadership Reading List April 2024.pdfMatthew Sinclair
 
Call girls Service in Ajman 0505086370 Ajman call girls
Call girls Service in Ajman 0505086370 Ajman call girlsCall girls Service in Ajman 0505086370 Ajman call girls
Call girls Service in Ajman 0505086370 Ajman call girlsMonica Sydney
 
Best SEO Services Company in Dallas | Best SEO Agency Dallas
Best SEO Services Company in Dallas | Best SEO Agency DallasBest SEO Services Company in Dallas | Best SEO Agency Dallas
Best SEO Services Company in Dallas | Best SEO Agency DallasDigicorns Technologies
 
APNIC Policy Roundup, presented by Sunny Chendi at the 5th ICANN APAC-TWNIC E...
APNIC Policy Roundup, presented by Sunny Chendi at the 5th ICANN APAC-TWNIC E...APNIC Policy Roundup, presented by Sunny Chendi at the 5th ICANN APAC-TWNIC E...
APNIC Policy Roundup, presented by Sunny Chendi at the 5th ICANN APAC-TWNIC E...APNIC
 
Vip Firozabad Phone 8250092165 Escorts Service At 6k To 30k Along With Ac Room
Vip Firozabad Phone 8250092165 Escorts Service At 6k To 30k Along With Ac RoomVip Firozabad Phone 8250092165 Escorts Service At 6k To 30k Along With Ac Room
Vip Firozabad Phone 8250092165 Escorts Service At 6k To 30k Along With Ac Roommeghakumariji156
 
Local Call Girls in Seoni 9332606886 HOT & SEXY Models beautiful and charmin...
Local Call Girls in Seoni  9332606886 HOT & SEXY Models beautiful and charmin...Local Call Girls in Seoni  9332606886 HOT & SEXY Models beautiful and charmin...
Local Call Girls in Seoni 9332606886 HOT & SEXY Models beautiful and charmin...kumargunjan9515
 
哪里办理美国迈阿密大学毕业证(本硕)umiami在读证明存档可查
哪里办理美国迈阿密大学毕业证(本硕)umiami在读证明存档可查哪里办理美国迈阿密大学毕业证(本硕)umiami在读证明存档可查
哪里办理美国迈阿密大学毕业证(本硕)umiami在读证明存档可查ydyuyu
 
一比一原版(Offer)康考迪亚大学毕业证学位证靠谱定制
一比一原版(Offer)康考迪亚大学毕业证学位证靠谱定制一比一原版(Offer)康考迪亚大学毕业证学位证靠谱定制
一比一原版(Offer)康考迪亚大学毕业证学位证靠谱定制pxcywzqs
 
Russian Escort Abu Dhabi 0503464457 Abu DHabi Escorts
Russian Escort Abu Dhabi 0503464457 Abu DHabi EscortsRussian Escort Abu Dhabi 0503464457 Abu DHabi Escorts
Russian Escort Abu Dhabi 0503464457 Abu DHabi EscortsMonica Sydney
 
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样ayvbos
 
Meaning of On page SEO & its process in detail.
Meaning of On page SEO & its process in detail.Meaning of On page SEO & its process in detail.
Meaning of On page SEO & its process in detail.krishnachandrapal52
 
Tadepalligudem Escorts Service Girl ^ 9332606886, WhatsApp Anytime Tadepallig...
Tadepalligudem Escorts Service Girl ^ 9332606886, WhatsApp Anytime Tadepallig...Tadepalligudem Escorts Service Girl ^ 9332606886, WhatsApp Anytime Tadepallig...
Tadepalligudem Escorts Service Girl ^ 9332606886, WhatsApp Anytime Tadepallig...meghakumariji156
 
APNIC Updates presented by Paul Wilson at ARIN 53
APNIC Updates presented by Paul Wilson at ARIN 53APNIC Updates presented by Paul Wilson at ARIN 53
APNIC Updates presented by Paul Wilson at ARIN 53APNIC
 
best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...
best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...
best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...kajalverma014
 
Abu Dhabi Escorts Service 0508644382 Escorts in Abu Dhabi
Abu Dhabi Escorts Service 0508644382 Escorts in Abu DhabiAbu Dhabi Escorts Service 0508644382 Escorts in Abu Dhabi
Abu Dhabi Escorts Service 0508644382 Escorts in Abu DhabiMonica Sydney
 
20240508 QFM014 Elixir Reading List April 2024.pdf
20240508 QFM014 Elixir Reading List April 2024.pdf20240508 QFM014 Elixir Reading List April 2024.pdf
20240508 QFM014 Elixir Reading List April 2024.pdfMatthew Sinclair
 
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查ydyuyu
 
2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs
2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs
2nd Solid Symposium: Solid Pods vs Personal Knowledge GraphsEleniIlkou
 

Último (20)

Nagercoil Escorts Service Girl ^ 9332606886, WhatsApp Anytime Nagercoil
Nagercoil Escorts Service Girl ^ 9332606886, WhatsApp Anytime NagercoilNagercoil Escorts Service Girl ^ 9332606886, WhatsApp Anytime Nagercoil
Nagercoil Escorts Service Girl ^ 9332606886, WhatsApp Anytime Nagercoil
 
20240509 QFM015 Engineering Leadership Reading List April 2024.pdf
20240509 QFM015 Engineering Leadership Reading List April 2024.pdf20240509 QFM015 Engineering Leadership Reading List April 2024.pdf
20240509 QFM015 Engineering Leadership Reading List April 2024.pdf
 
Call girls Service in Ajman 0505086370 Ajman call girls
Call girls Service in Ajman 0505086370 Ajman call girlsCall girls Service in Ajman 0505086370 Ajman call girls
Call girls Service in Ajman 0505086370 Ajman call girls
 
Best SEO Services Company in Dallas | Best SEO Agency Dallas
Best SEO Services Company in Dallas | Best SEO Agency DallasBest SEO Services Company in Dallas | Best SEO Agency Dallas
Best SEO Services Company in Dallas | Best SEO Agency Dallas
 
APNIC Policy Roundup, presented by Sunny Chendi at the 5th ICANN APAC-TWNIC E...
APNIC Policy Roundup, presented by Sunny Chendi at the 5th ICANN APAC-TWNIC E...APNIC Policy Roundup, presented by Sunny Chendi at the 5th ICANN APAC-TWNIC E...
APNIC Policy Roundup, presented by Sunny Chendi at the 5th ICANN APAC-TWNIC E...
 
Vip Firozabad Phone 8250092165 Escorts Service At 6k To 30k Along With Ac Room
Vip Firozabad Phone 8250092165 Escorts Service At 6k To 30k Along With Ac RoomVip Firozabad Phone 8250092165 Escorts Service At 6k To 30k Along With Ac Room
Vip Firozabad Phone 8250092165 Escorts Service At 6k To 30k Along With Ac Room
 
Local Call Girls in Seoni 9332606886 HOT & SEXY Models beautiful and charmin...
Local Call Girls in Seoni  9332606886 HOT & SEXY Models beautiful and charmin...Local Call Girls in Seoni  9332606886 HOT & SEXY Models beautiful and charmin...
Local Call Girls in Seoni 9332606886 HOT & SEXY Models beautiful and charmin...
 
call girls in Anand Vihar (delhi) call me [🔝9953056974🔝] escort service 24X7
call girls in Anand Vihar (delhi) call me [🔝9953056974🔝] escort service 24X7call girls in Anand Vihar (delhi) call me [🔝9953056974🔝] escort service 24X7
call girls in Anand Vihar (delhi) call me [🔝9953056974🔝] escort service 24X7
 
哪里办理美国迈阿密大学毕业证(本硕)umiami在读证明存档可查
哪里办理美国迈阿密大学毕业证(本硕)umiami在读证明存档可查哪里办理美国迈阿密大学毕业证(本硕)umiami在读证明存档可查
哪里办理美国迈阿密大学毕业证(本硕)umiami在读证明存档可查
 
一比一原版(Offer)康考迪亚大学毕业证学位证靠谱定制
一比一原版(Offer)康考迪亚大学毕业证学位证靠谱定制一比一原版(Offer)康考迪亚大学毕业证学位证靠谱定制
一比一原版(Offer)康考迪亚大学毕业证学位证靠谱定制
 
Russian Escort Abu Dhabi 0503464457 Abu DHabi Escorts
Russian Escort Abu Dhabi 0503464457 Abu DHabi EscortsRussian Escort Abu Dhabi 0503464457 Abu DHabi Escorts
Russian Escort Abu Dhabi 0503464457 Abu DHabi Escorts
 
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样
 
Meaning of On page SEO & its process in detail.
Meaning of On page SEO & its process in detail.Meaning of On page SEO & its process in detail.
Meaning of On page SEO & its process in detail.
 
Tadepalligudem Escorts Service Girl ^ 9332606886, WhatsApp Anytime Tadepallig...
Tadepalligudem Escorts Service Girl ^ 9332606886, WhatsApp Anytime Tadepallig...Tadepalligudem Escorts Service Girl ^ 9332606886, WhatsApp Anytime Tadepallig...
Tadepalligudem Escorts Service Girl ^ 9332606886, WhatsApp Anytime Tadepallig...
 
APNIC Updates presented by Paul Wilson at ARIN 53
APNIC Updates presented by Paul Wilson at ARIN 53APNIC Updates presented by Paul Wilson at ARIN 53
APNIC Updates presented by Paul Wilson at ARIN 53
 
best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...
best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...
best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...
 
Abu Dhabi Escorts Service 0508644382 Escorts in Abu Dhabi
Abu Dhabi Escorts Service 0508644382 Escorts in Abu DhabiAbu Dhabi Escorts Service 0508644382 Escorts in Abu Dhabi
Abu Dhabi Escorts Service 0508644382 Escorts in Abu Dhabi
 
20240508 QFM014 Elixir Reading List April 2024.pdf
20240508 QFM014 Elixir Reading List April 2024.pdf20240508 QFM014 Elixir Reading List April 2024.pdf
20240508 QFM014 Elixir Reading List April 2024.pdf
 
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查
 
2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs
2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs
2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs
 

CQRS + Event Sourcing in PHP