Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CQRS and Event Sourcing: Software architecture in a higher level, for everyone!

CQRS and Event Sourcing: Software architecture in a higher level, for everyone!

Event: PHPFest Russia 2020
URL: https://2020.phpfest.ru/

CQRS (Command Query Responsibility Segregation) and Event Sourcing are subjects that you might have heard around. It's a new way of thinking in software development, aiming better performance and domain visibility. This talk's goal is to give you an introductory overview of these topics, with some practical examples in PHP about how things work together.


Junior Grossi

October 23, 2020

More Decks by Junior Grossi

Other Decks in Programming


  1. CQRS & EVENT SOURCING software architecture in a higher level,

    for everyone!
  2. hey, I'm Junior Grossi twitter.com/junior_grossi github.com/jgrossi grossi.dev

  3. None
  4. https://github.com/corcel/corcel

  5. PHPMG Conference 2019 21/09/2019 - Belo Horizonte, Brazil https://conf.phpmg.com

  6. I do love #elephpants https://elephpant.me/herd/junior_grossi

  7. remote-first company php / golang / java / js https://glofox.com/careers

  8. / 2004 php / 2016 / 2019

  9. so... let's start!

  10. CQRS & EVENT SOURCING it's not a software architecture pattern

    it's an independent design pattern
  11. WHERE DOES IT FIT? you don't have to change your

    current architecture it can be applied to one or more use cases (including that crazy legacy code you work with)
  12. CQRS COMMAND QUERY RESPONSIBILITY SEGREGATION (break the responsibility between "reading"

    and "writing") Command: write operations Query: read operations
  13. BUT, WHY? scalability / performance (all data might be obsolete

  14. class UserController { public function create(Request $request): ResponseInterfac { $user

    = User::create($request->all()); return new JsonResponse($user, 201); } public function show(Request $request): ResponseInterface { $user = User::find($request->get('id')); return new JsonResponse($user, 200); } }
  15. ALERT! we're using an ORM! (we use it before and

    after the DB operations)

  17. Actor RegisterUser ORM DB ORM User::create() JSON HTTP User instance

  18. CQRS is event-based async communication / queue (use the best

    of each "storage")
  19. None

  21. IMPORTANT CONCEPT Command Handler (commonly used for writing operations)

  22. 1 Command for 1 Handler RegisterUserCommand + RegisterUserHandler (command for

    data + handler for logic)
  23. POST /v1/users final class RegisterUserController { public function __construct( private

    RegisterUserHandler $handler ) {} public function __invoke( RequestInterface $request ): ResponseInterface { $userId = Uuid::uuid4(); $this->handler->handle( new RegisterUserCommand( new UserId($userId), $request->getParsedBody()['name'], $request->getParsedBody()['email'] ) ); return new JsonResponse(['id' => $userId], 201); } }
  24. COMMAND: FOR THE DATA final class RegisterUserCommand { public function

    __construct( private UserId $id, private string $name, private string $email ) {} // getId(), getName(), getEmail() }
  25. HANDLER: FOR THE LOGIC final class RegisterUserHandler { public function

    __construct( private UserRepositoryInterface $userRepository ) {} public function handle(RegisterUserCommand $command): void { $this->userRepository->add( new User( $command->getId(), $command->getName(), $command->getEmail() ) ); } }
  26. remember: CQRS is event-based EVENTS DON'T RETURN DATA (I'm gonna

    create a user but won't return it) CQRS: writing operations don't return
  27. WTF? how won't I return the user? (you don't need

    it, actually!)
  28. EVENT BASED I can send a Handler to the queue,

    for example! CommandBus (quick return to the user)
  29. POST /v1/users final class RegisterUserController { public function __construct( private

    MessageBus $commandBus ) {} public function __invoke( RequestInterface $request ): ResponseInterface { $userId = Uuid::uuid4(); $this->commandBus->dispatch( new RegisterUserCommand( new UserId($userId), $request->getParsedBody()['name'], $request->getParsedBody()['email'] ) ); return new JsonResponse(['id' => $userId], 201); } }
  30. WHAT ABOUT FAILURE? the queue message will be processed again

    when possible (the DB can be down, but the queue will still be there)
  31. Actor RegisterUserHandler Queue ID Data Processing


  33. final class UserFinder { public function __construct( private Connection $connection

    ) {} public function find(UserId $id): array { $userData = $this->connection ->findOne(['id' => (string)$id]); if (!$userData) { throw UserNotFoundException::withId($id); } return $userData; } }
  34. why Connection? we don't need Repository or ORM! plain data

    (array) is enough (because the final goal is to return a JSON!)
  35. WHERE TO APPLY? microservices (async communication) slow use cases (+

  36. EVENT SOURCING "EVERYTHING IS AN EVENT" (state changes are considered

  37. USE CASE ChangeEmailHandler (change the user's email address)

  38. id name email 1 Junior Grossi junior@grossi.dev $handler->handle( new ChangeEmailCommand(

    userId: new UserId(1), email: 'jgrossi@phpfest.ru' ) );
  39. WHAT'S EXPECTED? id name email 1 Junior Grossi jgrossi@phpfest.ru

  40. WHAT'S THE PROBLEM? what was the change? what was the

    reason? (we don't have that, unfortunately!) or maybe you can check the logs
  41. this is Event Sourcing! [ { event: "UserWasRegistered", createdAt: "2020-10-19

    07:33:18", payload: { id: 1, name: "Junior Grossi", email: "junior@grossi.dev" } }, { event: "UserEmailWasUpdated", createdAt: "2020-10-23 07:33:18", payload: { id: 1, newEmail: "jgrossi@phpfest.ru" } } ]
  42. WHY TO USE EVENT SOURCING? control state changes rewrite history

    when something bad happens strategic decision-making (behaviour)
  43. all events are written in the past: OrderPaid (register step-by-step

    and data)
  44. USE CASES (EXAMPLES) bugfixes: find the exact point of failure

    (check what's the change that caused the bug) microservices: centralise history of changes between services rewrite the DB history (literally travel back in time to fix something)
  45. event sourcing is part of your domain! you should not

    use any framework
  46. REFERENCE github.com/prooph/common github.com/prooph/event-sourcing (deprecated) use as reference only! keep the

    domain yours!
  47. IMPORTANT DEFINITION what are Aggregates? (used to represent the "what

  48. AGGREGATES they represent the consistency of a state ensure I

    can change from one state to another (validation rules at business level)
  49. BUSINESS RULES "a shopping cart always starts empty" "a product

    can be added only if available" "# of items of a product cannot be greater than stock"
  50. final class ShoppingCart extends AggregateRoot { public function __construct( private

    ShoppingSession $shoppingSession, private BasketId $basketId, private array $products = [] ) { $this->record( new ShoppingSessionStarted($basketId, [ 'shoppingSession' => (string)$shoppingSession, ]) ); } public function addProduct( ProductId $productId, int $quantity = 1 ): void { $this->record( new ProductAdded($basketId, [ 'productId' => (string)$productId, 'quantity' => $quantity, ]) ); } }
  51. more information how they work http://docs.getprooph.org/tutorial/event_sourcing_basics.html


  53. CQRS different responsibilities between reading and writing Command + Handler

    = Event CommandBus => Queue (sync DB based on those events)
  54. EVENT SOURCING register every state change (using events) Events +

    Aggregates (register the event with a payload - when/what) (history of all state changes)
  55. FINAL CONSIDERATIONS don't use CQRS / Event Sourcing in the

    whole project (only where you really need it)
  56. HOW TO START? apply CQRS first! Command + Handler pattern

    (Event Sourcing has a higher learning/implementation curve)
  57. THANK YOU! search on youtube for "grossi-code" http://twitter.com/junior_grossi http://speakerdeck.com/jgrossi