introduction to cqrs and event sourcing
TRANSCRIPT
![Page 1: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/1.jpg)
Introduction to CQRS and Event Sourcing
Samuel ROZE (@samuelroze)
![Page 2: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/2.jpg)
![Page 3: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/3.jpg)
![Page 4: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/4.jpg)
![Page 5: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/5.jpg)
The heart of software is its ability to solve domain-related
problems for its user— Eric Evans
![Page 6: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/6.jpg)
![Page 7: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/7.jpg)
![Page 8: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/8.jpg)
![Page 9: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/9.jpg)
Event Storming
![Page 10: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/10.jpg)
Event Sourcing
![Page 11: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/11.jpg)
Our domain
1. Create a "deployment"
2. Start a deployment
3. Realtime status of our deployment
![Page 12: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/12.jpg)
Our model is this...
An entity +------------------+ | Deployment | +------------------+ |- uuid: Uuid | |- sha1: string | |- status: string | |- startedBy: User | +------------------+
![Page 13: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/13.jpg)
What we could have doneA Doctrine mapping and.. a table!
+-------------------+ | deployment | +-------------------+ | uuid VARCHAR | | status VARCHAR | | sha1 VARCHAR | | startedBy VARCHAR | +-------------------+
![Page 14: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/14.jpg)
An other approachA set of events as the reference!
+-------------------------------------+ |DeploymentCreated |DeploymentStarted | | uuid: [...] | uuid: [...] | ... | datetime: [...] | datetime: [...] | | sha1: [...] | startedBy: [...] | +------------------+------------------+
![Page 15: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/15.jpg)
Why using events?1. Closer to the business language
2. Keep the information about what happened3. Easy to spread the logic across services
4. No coupling between domain and storage5. Append-only it's a LOT easier to scale
![Page 16: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/16.jpg)
![Page 17: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/17.jpg)
Let's get started !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
![Page 18: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/18.jpg)
@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; }}
![Page 19: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/19.jpg)
@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' ); }}
![Page 20: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/20.jpg)
@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'); }}
![Page 21: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/21.jpg)
Erm...$ bin/behat -fprogressFFFF
2 scenarios (0 passed)4 steps (0 passed)0m0.12s (40.89Mb)
![Page 22: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/22.jpg)
An eventinterface DeploymentEvent{ public function getDeploymentUuid() : UuidInterface;
public function getDateTime(): \DateTimeInterface;}
![Page 23: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/23.jpg)
'DeploymentCreated' eventfinal 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; }}
![Page 24: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/24.jpg)
Event capabilitytrait EventsCapability{ private $events = [];
protected function raise(DeploymentEvent $event) { $this->events[] = $event; }
public function raisedEvents() : array { return $this->events; }}
![Page 25: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/25.jpg)
The aggregatefinal class Deployment{ use EventsCapability;
private function __construct() {}}
![Page 26: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/26.jpg)
Creating the object from eventsfinal class Deployment{ // ... public static function fromEvents(array $events) { $deployment = new self();
foreach ($events as $event) { $deployment->apply($event); }
return $deployment; }}
![Page 27: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/27.jpg)
Building the object statefinal class Deployment{ private $uuid;
// ...
private function apply(DeploymentEvent $event) { if ($event instanceof DeploymentCreated) { $this->uuid = $event->getDeploymentUuid(); } }}
![Page 28: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/28.jpg)
Create... from the beginning!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; }}
![Page 29: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/29.jpg)
Wourah!$ bin/behat -fprogress....
2 scenarios (2 passed)4 steps (4 passed)0m0.12s (40.89Mb)
![Page 30: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/30.jpg)
![Page 31: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/31.jpg)
Starting a deployment !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
![Page 32: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/32.jpg)
@Given a deployment was created and started
public function aDeploymentWasCreatedAndStarted(){ $uuid = Uuid::uuid4();
$this->deployment = Deployment::fromEvents([ new DeploymentCreated($uuid, '921103d'), new DeploymentStarted($uuid), ]);}
![Page 33: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/33.jpg)
@When I start the deployment
public function iStartTheDeployment(){ try { $this->deployment->start(); } catch (\Throwable $e) { $this->exception = $e; }}
![Page 34: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/34.jpg)
'start'ing a deploymentfinal 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)); }}
![Page 35: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/35.jpg)
Keeping trace of the statusfinal class Deployment{ private $started = false; // ...
public function apply(DeploymentEvent $event) { // ... if ($event instanceof DeploymentStarted) { $this->started = true; } }}
![Page 36: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/36.jpg)
That's too fast...$ bin/behat -fprogress.........
4 scenarios (4 passed)10 steps (10 passed)0m0.31s (41.22Mb)
![Page 37: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/37.jpg)
We are done!...with our domain
![Page 38: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/38.jpg)
Repositories & Persistence
![Page 39: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/39.jpg)
Event Storeinterface EventStore{ public function findByDeploymentUuid(UuidInterface $uuid) : array;
public function add(DeploymentEvent $event);}
Implementation detail: InMemory / Doctrine / Redis / GetEventStore / ...
![Page 40: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/40.jpg)
Our repository contractinterface DeploymentRepository{ public function find(UuidInterface $uuid) : Deployment;}
![Page 41: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/41.jpg)
The event-based implementationfinal class EventBasedDeploymentRepository implements DeploymentRepository{ public function __construct(EventStore $eventStore) { /** .. **/ }
public function find(UuidInterface $uuid) : Deployment { return Deployment::fromEvents( $this->eventStore->findByDeploymentUuid($uuid) ); }}
![Page 42: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/42.jpg)
CQRS?
![Page 43: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/43.jpg)
![Page 44: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/44.jpg)
![Page 45: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/45.jpg)
![Page 46: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/46.jpg)
![Page 47: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/47.jpg)
![Page 48: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/48.jpg)
![Page 49: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/49.jpg)
![Page 50: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/50.jpg)
![Page 51: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/51.jpg)
Projections!The "read model"
· Creates a read-optimized view of our model· As many projection as you want
· Any kind of backend (database, API, queue, ...)
![Page 52: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/52.jpg)
final class DeploymentFirebaseProjector{ public function __construct( DeploymentRepository $repository, FirebaseStorage $storage ) { /* ... */ }
public function notify(DeploymentEvent $event) { $uuid = $event->getDeploymentUuid(); $deployment = $this->repository->find($uuid);
$this->storage->store('deployments/'.$uuid, [ 'started' => $deployment->isStarted(), ]); }}
![Page 53: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/53.jpg)
We've done it!1. Create a "deployment"
2. Start a deployment
3. Realtime status of our deployment
![Page 54: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/54.jpg)
Thank you!@samuelroze
continuouspipe.iohttps://joind.in/talk/03af6
![Page 55: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/55.jpg)
SimpleBus· Written by Matthias Noback
http://simplebus.github.io/SymfonyBridge/
# app/config/config.ymlevent_bus: logging: ~
command_bus: logging: ~
![Page 56: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/56.jpg)
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); }}
![Page 57: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/57.jpg)
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); }}
![Page 58: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/58.jpg)
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); } }}
![Page 59: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/59.jpg)
The plumbing<service id="app.controller.deployment" class="AppBundle\Controller\DeploymentController"> <argument type="service" id="command_bus" /></service>
<service id="app.handler.create_deployment" class="App\Deployment\Handler\CreateDeploymentHandler"> <argument type="service" id="event_bus" />
<tag name="command_handler" handles="App\Command\CreateDeployment" /></service>
![Page 60: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/60.jpg)
What do we have right now?1. Send a command from an HTTP API
2. The command handler talks to our domain3. Domain raise an event
4. The event is dispatched to the event bus
![Page 61: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/61.jpg)
Storing our eventsfinal 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); }}
![Page 62: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/62.jpg)
We <3 XML<service id="app.event_bus.middleware.store_events" class="App\EventBus\Middleware\StoreEvents"> <argument type="service" id="event_store" />
<tag name="event_bus_middleware" /></service>
![Page 63: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/63.jpg)
Our events are stored!...so we can get our
Deployment from the repository
![Page 64: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/64.jpg)
Let's start our deployment!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() )); }}
![Page 65: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/65.jpg)
The handlerfinal 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); } }}
![Page 66: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/66.jpg)
The plumbing<service id="app.deployment.auto_start.starts_when_created" class="App\Deployment\AutoStart\StartsWhenCreated"> <argument type="service" id="command_bus" />
<tag name="event_subscriber" subscribes_to="App\Event\DeploymentCreated" /></service>
<service id="app.deployment.handler.start_deployment" class="App\Deployment\Handler\StartDeploymentHandler"> <argument type="service" id="app.deployment_repository" /> <argument type="service" id="event_bus" />
<tag name="command_handler" handles="App\Command\StartDeployment" /></service>
![Page 67: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/67.jpg)
What happened?[...]
4. A dispatched DeploymentCreated event5. A listener created a StartDeployment
command6. The command handler called the start
method on the Deployment7. The domain validated and raised a
DeploymentStarted event8. The DeploymentStarted was dispatched on
![Page 68: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/68.jpg)
You'll go further...
![Page 69: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/69.jpg)
final class Deployment{ // ...
public function finishedBuild(Build $build) { if ($build->isFailure()) { return $this->raise(new DeploymentFailed($this->uuid)); }
$this->builtImages[] = $build->getImage(); if (count($this->builtImages) == count($this->images)) { $this->raise(new DeploymentSuccessful($this->uuid)); } }}
![Page 70: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/70.jpg)
Dependencies... the wrong wayfinal class Deployment{ private $notifier;
public function __construct(NotifierInterface $notifier) { /* .. */ }
public function notify() { $this->notifier->notify($this); }}
![Page 71: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/71.jpg)
Dependencies... the right wayfinal class Deployment{ public function notify(NotifierInterface $notifier) { $notifier->notify($this); }}
![Page 72: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/72.jpg)
Testing! (layers)1. Use your domain objects
2. Create commands and read your event store3. Uses your API and projections
![Page 73: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/73.jpg)
What we just achieved1. Incoming HTTP requests
2. Commands to the command bus3. Handlers talk to your domain
4. Domain produces events5. Events are stored and dispatched
6. Projections built for fast query
![Page 74: Introduction to CQRS and Event Sourcing](https://reader034.vdocument.in/reader034/viewer/2022052214/58f9ad40760da3da068b966c/html5/thumbnails/74.jpg)
Thank you!@samuelroze
continuouspipe.iohttps://joind.in/talk/03af6