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
Tweet

More Decks by Junior Grossi

Other Decks in Programming

Transcript

  1. 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)
  2. CQRS COMMAND QUERY RESPONSIBILITY SEGREGATION (break the responsibility between "reading"

    and "writing") Command: write operations Query: read operations
  3. 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); } }
  4. 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); } }
  5. COMMAND: FOR THE DATA final class RegisterUserCommand { public function

    __construct( private UserId $id, private string $name, private string $email ) {} // getId(), getName(), getEmail() }
  6. 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() ) ); } }
  7. 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
  8. EVENT BASED I can send a Handler to the queue,

    for example! CommandBus (quick return to the user)
  9. 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); } }
  10. WHAT ABOUT FAILURE? the queue message will be processed again

    when possible (the DB can be down, but the queue will still be there)
  11. 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; } }
  12. why Connection? we don't need Repository or ORM! plain data

    (array) is enough (because the final goal is to return a JSON!)
  13. 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
  14. this is Event Sourcing! [ { event: "UserWasRegistered", createdAt: "2020-10-19

    07:33:18", payload: { id: 1, name: "Junior Grossi", email: "[email protected]" } }, { event: "UserEmailWasUpdated", createdAt: "2020-10-23 07:33:18", payload: { id: 1, newEmail: "[email protected]" } } ]
  15. WHY TO USE EVENT SOURCING? control state changes rewrite history

    when something bad happens strategic decision-making (behaviour)
  16. 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)
  17. AGGREGATES they represent the consistency of a state ensure I

    can change from one state to another (validation rules at business level)
  18. 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"
  19. 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, ]) ); } }
  20. CQRS different responsibilities between reading and writing Command + Handler

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

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

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

    (Event Sourcing has a higher learning/implementation curve)