Founder and CEO of prooph software GmbH
Founder and core contributor prooph components
contact@prooph.de
Supported Web Frameworks
or any other framework/codebase supporting container-interop & interop-config
Complex
Distributed
Chaotic
Informational message
Communicate facts that have happened
Relevant to the business
OrderPlaced
OrderPayed
OrderShipped
<?php
final class OrderService
{
private $entityManager;
private $orderRepositoy;
private $eventPublisher;
public function placeOrder(string $orderId, OrderItem ...$items): void
{
$order = $this->orderRepostoy->get($orderId);
$this->entityManager->beginTransaction();
try {
$order->place($items);
$this->entityManager->commit();
} catch(\Throwable $error) {
$this->entityManager->rollBack();
throw $error;
}
$event = OrderPlaced::fromOrder($order);
$this->eventPublisher->publish($event);
}
}
<?php
final class PlaceOrderHandler
{
private $eventStore;
private $eventPublisher;
public function handle(PlaceOrder $command): void
{
//Note: Loading/saving is normally done by event sourcing repository
$orderStream = new StreamName('order-'.$command->orderId());
$history = $this->eventStore->load($orderStream);
$order = Order::reconstituteFromHistory($history);
$newEvents = $order->place($command->items());
//Atomic operation
$this->eventStore->appendTo($orderStream, $newEvents);
$this->eventPublisher->publish($newEvents);
}
}
# E1 at t1: UserRegistered
userId: 1
name: John Doe
email: doe@fake.com
# E2 at t2: UsernameChanged
userId: 1
oldName: John Doe
newName: Alex
# E3 at t3: EmailChanged
userId: 1
oldEmail: doe@fake.com
newEmail: contact@prooph.de
_______________________________________________
# current state (t:now) = ((E1 + E2) + E3):
userId: 1
name: Alex
email: contact@prooph.de
version: 3
UserRegistered
UsernameChanged
ChangeEmailFailed
{
E1 + E2
Change Email
UserRegistered
UsernameChanged
5 min
7 min
Who has changed username within 5 min after registration?
UserRegistered
UsernameChanged
Gateway
Write Model
Read Model
Gateway
Write Model
Read Model
Event Store
Projection
Cache
prooph_service_bus:
command_buses:
todo_command_bus:
router:
type: 'prooph_service_bus.command_bus_router'
event_buses:
todo_event_bus:
plugins:
- 'prooph_service_bus.on_event_invoke_strategy'
router:
type: 'prooph_service_bus.event_bus_router'
routes:
'Prooph\ProophessorDo\Model\Todo\Event\TodoAssigneeWasReminded':
- 'Prooph\ProophessorDo\ProcessManager\SendTodoReminderMailSubscriber'
'Prooph\ProophessorDo\Model\Todo\Event\TodoWasMarkedAsExpired':
- 'Prooph\ProophessorDo\ProcessManager\SendTodoDeadlineExpiredMailSubscriber'
Prooph\ProophessorDo\Model\Todo\Handler\MarkTodoAsDoneHandler:
arguments: ['@todo_list']
public: true
tags:
- {
name: 'prooph_service_bus.todo_command_bus.route_target',
message_detection: true
}
class MarkTodoAsDoneHandler
{
/**
* @var TodoList
*/
private $todoList;
public function __construct(TodoList $todoList)
{
$this->todoList = $todoList;
}
public function __invoke(MarkTodoAsDone $command): void
{
$todo = $this->todoList->get($command->todoId());
if (! $todo) {
throw TodoNotFound::withTodoId($command->todoId());
}
$todo->markAsDone();
$this->todoList->save($todo);
}
}
services:
Prooph\EventStore\Pdo\MySqlEventStore:
arguments:
- '@prooph_event_store.message_factory',
- '@doctrine.pdo.connection',
- '@prooph_event_store.single_stream_strategy'
doctrine.pdo.connection:
class: PDO
factory: ['@database_connection', getWrappedConnection]
prooph_event_store.single_stream_strategy:
class: Prooph\EventStore\Pdo\PersistenceStrategy\MySqlSingleStreamStrategy
prooph_event_sourcing.aggregate_translator:
class: Prooph\EventSourcing\EventStoreIntegration\AggregateTranslator
prooph_event_store:
stores:
todo_store:
event_store: Prooph\EventStore\Pdo\MySqlEventStore
repositories:
todo_list:
repository_class: Infrastructure\Repository\EventStoreTodoList
aggregate_type: Model\Todo\Todo
aggregate_translator: prooph_event_sourcing.aggregate_translator
final class Todo extends AggregateRoot implements Entity
{
//...
/**
* @throws Exception\TodoNotOpen
*/
public function markAsDone(): void
{
$status = TodoStatus::DONE();
if (! $this->status->is(TodoStatus::OPEN())) {
throw Exception\TodoNotOpen::triedStatus($status, $this);
}
$this->recordThat(
TodoWasMarkedAsDone::fromStatus(
$this->todoId,
$this->status,
$status,
$this->assigneeId
)
);
}
//...
}