Slide 1

Slide 1 text

CQRS & EVENT SOURCING software architecture in a higher level, for everyone!

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

https://github.com/corcel/corcel

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

/ 2004 php / 2016 / 2019

Slide 9

Slide 9 text

so... let's start!

Slide 10

Slide 10 text

CQRS & EVENT SOURCING it's not a software architecture pattern it's an independent design pattern

Slide 11

Slide 11 text

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)

Slide 12

Slide 12 text

CQRS COMMAND QUERY RESPONSIBILITY SEGREGATION (break the responsibility between "reading" and "writing") Command: write operations Query: read operations

Slide 13

Slide 13 text

BUT, WHY? scalability / performance (all data might be obsolete already)

Slide 14

Slide 14 text

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); } }

Slide 15

Slide 15 text

ALERT! we're using an ORM! (we use it before and after the DB operations)

Slide 16

Slide 16 text

WHAT ABOUT THE DB?

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

CQRS is event-based async communication / queue (use the best of each "storage")

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

WRITING READING

Slide 21

Slide 21 text

IMPORTANT CONCEPT Command Handler (commonly used for writing operations)

Slide 22

Slide 22 text

1 Command for 1 Handler RegisterUserCommand + RegisterUserHandler (command for data + handler for logic)

Slide 23

Slide 23 text

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); } }

Slide 24

Slide 24 text

COMMAND: FOR THE DATA final class RegisterUserCommand { public function __construct( private UserId $id, private string $name, private string $email ) {} // getId(), getName(), getEmail() }

Slide 25

Slide 25 text

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() ) ); } }

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

WTF? how won't I return the user? (you don't need it, actually!)

Slide 28

Slide 28 text

EVENT BASED I can send a Handler to the queue, for example! CommandBus (quick return to the user)

Slide 29

Slide 29 text

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); } }

Slide 30

Slide 30 text

WHAT ABOUT FAILURE? the queue message will be processed again when possible (the DB can be down, but the queue will still be there)

Slide 31

Slide 31 text

Actor RegisterUserHandler Queue ID Data Processing

Slide 32

Slide 32 text

WHAT ABOUT READING? query - CQRS

Slide 33

Slide 33 text

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; } }

Slide 34

Slide 34 text

why Connection? we don't need Repository or ORM! plain data (array) is enough (because the final goal is to return a JSON!)

Slide 35

Slide 35 text

WHERE TO APPLY? microservices (async communication) slow use cases (+ performance)

Slide 36

Slide 36 text

EVENT SOURCING "EVERYTHING IS AN EVENT" (state changes are considered events)

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

id name email 1 Junior Grossi junior@grossi.dev $handler->handle( new ChangeEmailCommand( userId: new UserId(1), email: 'jgrossi@phpfest.ru' ) );

Slide 39

Slide 39 text

WHAT'S EXPECTED? id name email 1 Junior Grossi jgrossi@phpfest.ru

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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" } } ]

Slide 42

Slide 42 text

WHY TO USE EVENT SOURCING? control state changes rewrite history when something bad happens strategic decision-making (behaviour)

Slide 43

Slide 43 text

all events are written in the past: OrderPaid (register step-by-step and data)

Slide 44

Slide 44 text

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)

Slide 45

Slide 45 text

event sourcing is part of your domain! you should not use any framework

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

IMPORTANT DEFINITION what are Aggregates? (used to represent the "what happened")

Slide 48

Slide 48 text

AGGREGATES they represent the consistency of a state ensure I can change from one state to another (validation rules at business level)

Slide 49

Slide 49 text

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"

Slide 50

Slide 50 text

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, ]) ); } }

Slide 51

Slide 51 text

more information how they work http://docs.getprooph.org/tutorial/event_sourcing_basics.html

Slide 52

Slide 52 text

QUICK REVIEW

Slide 53

Slide 53 text

CQRS different responsibilities between reading and writing Command + Handler = Event CommandBus => Queue (sync DB based on those events)

Slide 54

Slide 54 text

EVENT SOURCING register every state change (using events) Events + Aggregates (register the event with a payload - when/what) (history of all state changes)

Slide 55

Slide 55 text

FINAL CONSIDERATIONS don't use CQRS / Event Sourcing in the whole project (only where you really need it)

Slide 56

Slide 56 text

HOW TO START? apply CQRS first! Command + Handler pattern (Event Sourcing has a higher learning/implementation curve)

Slide 57

Slide 57 text

THANK YOU! search on youtube for "grossi-code" http://twitter.com/junior_grossi http://speakerdeck.com/jgrossi