Event Sourcing is the idea that every state of your application can be represented by a sequence of events. Using these two principles as the heart of a system or an application is quite common but can be challenging if we don’t use the right tools or architecture.
15. Whyusingevents?
1. Closer to the business language
2. Keep the information about what happened
3. Easy to spread the logic across services
4. No coupling between domain and storage
5. Append-only it's a LOT easier to scale
16.
17. Let'sgetstarted!
Scenario: A deployment need to have a valid SHA-1
When I create a deployment for "123"
Then the deployment should not be valid
Scenario: Deployment for a valid SHA-1
When I create a deployment for "921103d"
Then a deployment should be created
18. @When I create a deployment for :sha1
public function iCreateADeploymentFor(string $sha1)
{
try {
$this->deployment = Deployment::create(
Uuid::uuid4(),
$sha1
);
} catch (Throwable $e) {
$this->exception = $e;
}
}
19. @Then the deployment should not be valid
public function theDeploymentShouldNotBeValid()
{
if (!$this->exception instanceof InvalidArgumentException) {
throw new RuntimeException(
'The exception found, if any, is not matching'
);
}
}
20. @Then a deployment should be created
public function aDeploymentShouldBeCreated()
{
$events = $this->deployment->raisedEvents();
$matchingEvents = array_filter($events, function(DeploymentEvent $event) {
return $event instanceof DeploymentCreated;
});
if (count($matchingEvents) === 0) {
throw new RuntimeException('No deployment created found');
}
}
23. 'DeploymentCreated'event
final class DeploymentCreated implements DeploymentEvent
{
public function __construct(UuidInterface $uuid, string $sha1)
{ /* .. */ }
public function getDeploymentUuid() : UuidInterface
{
return $this->uuid;
}
public function getSha1() : string
{
return $this->sha1;
}
}
26. Creatingtheobjectfromevents
final class Deployment
{
// ...
public static function fromEvents(array $events)
{
$deployment = new self();
foreach ($events as $event) {
$deployment->apply($event);
}
return $deployment;
}
}
28. Create...fromthebeginning!
final class Deployment
{
// ...
public static function create(Uuid $uuid, string $sha1)
{
if (strlen($sha1) < 7) {
throw new InvalidArgumentException('It is not a valid SHA-1');
}
$createdEvent = new DeploymentCreated($uuid, $sha1);
$deployment = self::fromEvents([$createdEvent]);
$deployment->raise($createdEvent);
return $deployment;
}
}
31. Startingadeployment!
Scenario: A successfully created deployment can be started
Given a deployment was created
When I start the deployment
Then the deployment should be started
Scenario: A deployment can be started only once
Given a deployment was created and started
When I start the deployment
Then I should be told that the deployment has already started
32. @Given a deployment was created and started
public function aDeploymentWasCreatedAndStarted()
{
$uuid = Uuid::uuid4();
$this->deployment = Deployment::fromEvents([
new DeploymentCreated($uuid, '921103d'),
new DeploymentStarted($uuid),
]);
}
33. @When I start the deployment
public function iStartTheDeployment()
{
try {
$this->deployment->start();
} catch (Throwable $e) {
$this->exception = $e;
}
}
34. 'start'ingadeployment
final class Deployment
{
private $uuid;
private $started = false;
// ...
public function start()
{
if ($this->started) {
throw new RuntimeException('Deployment already started');
}
$this->raise(new DeploymentStarted($this->uuid));
}
}
41. Theevent-basedimplementation
final class EventBasedDeploymentRepository implements DeploymentRepository
{
public function __construct(EventStore $eventStore)
{ /** .. **/ }
public function find(UuidInterface $uuid) : Deployment
{
return Deployment::fromEvents(
$this->eventStore->findByDeploymentUuid($uuid)
);
}
}
55. SimpleBus
· Written by Matthias Noback
http://simplebus.github.io/SymfonyBridge/
# app/config/config.yml
event_bus:
logging: ~
command_bus:
logging: ~
56. final class DeploymentController
{
private $eventBus;
public function __construct(MessageBus $eventBus)
{ /* ... */ }
public function createAction(Request $request)
{
$deployment = Deployment::create(
Uuid::uuid4(),
$request->request->get('sha1')
);
foreach ($deployment->raisedEvents() as $event) {
$this->eventBus->handle($event);
}
return new Response(Response::HTTP_CREATED);
}
}
57. final class DeploymentController
{
private $commandBus;
public function __construct(MessageBus $commandBus)
{ /* ... */ }
public function createAction(Request $request)
{
$uuid = Uuid::uuid4();
$this->commandBus->handle(new CreateDeployment(
$uuid,
$request->request->get('sha1')
));
return new Response(Response::HTTP_CREATED);
}
}
58. final class CreateDeploymentHandler
{
private $eventBus;
public function __construct(MessageBus $eventBus)
{ /* ... */ }
public function handle(CreateDeployment $command)
{
$deployment = Deployment::create(
$command->getUuid(),
$command->getSha1()
);
foreach ($deployment->raisedEvents() as $event) {
$this->eventBus->handle($event);
}
}
}
60. Whatdowehaverightnow?
1. Send a command from an HTTP API
2. The command handler talks to our domain
3. Domain raise an event
4. The event is dispatched to the event bus
61. Storingourevents
final class DeploymentEventStoreMiddleware implements MessageBusMiddleware
{
private $eventStore;
public function __construct(EventStore $eventStore)
{
$this->eventStore = $eventStore;
}
public function handle($message, callable $next)
{
if ($message instanceof DeploymentEvent) {
$this->eventStore->add($message);
}
$next($message);
}
}
64. Let'sstartourdeployment!
final class StartDeploymentWhenCreated
{
private $commandBus;
public function __construct(MessageBus $commandBus)
{ /* ... */ }
public function notify(DeploymentCreated $event)
{
// There will be conditions here...
$this->commandBus->handle(new StartDeployment(
$event->getDeploymentUuid()
));
}
}
65. Thehandler
final class StartDeploymentHandler
{
public function __construct(DeploymentRepository $repository, MessageBus $eventBus)
{ /* ... */ }
public function handle(StartDeployment $command)
{
$deployment = $this->repository->find($command->getDeploymentUuid());
$deployment->start();
foreach ($deployment->raisedEvents() as $event) {
$this->eventBus->handle($event);
}
}
}
67. Whathappened?
[...]
4. A dispatched DeploymentCreated event
5. A listener created a StartDeployment
command
6. The command handler called the start
method on the Deployment
7. The domain validated and raised a
DeploymentStarted event
8. The DeploymentStarted was dispatched on
72. Testing!(layers)
1. Use your domain objects
2. Create commands and read your event store
3. Uses your API and projections
73. Whatwejustachieved
1. Incoming HTTP requests
2. Commands to the command bus
3. Handlers talk to your domain
4. Domain produces events
5. Events are stored and dispatched
6. Projections built for fast query