Slide 1

Slide 1 text

CQRS & EVENT SOURCING Arquitetura em alto nível, para todos os níveis!

Slide 2

Slide 2 text

Sou Junior Grossi twitter.com/junior_grossi github.com/jgrossi

Slide 3

Slide 3 text

Paciência!

Slide 4

Slide 4 text

https://github.com/corcel/corcel

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

https://glofox.com/careers

Slide 8

Slide 8 text

CQRS & EVENT SOURCING Não é um padrão de Arquitetura de So ware É um padrão de código independente

Slide 9

Slide 9 text

ONDE APLICAR? Não precisa mudar sua arquitetura Pode ser aplicado à um (ou mais) casos de uso (inclusive naquele código legado bizarro que você trabalha)

Slide 10

Slide 10 text

CQRS COMMAND QUERY RESPONSIBILITY SEGREGATION (separar responsabilidade de leitura e escrita)

Slide 11

Slide 11 text

MAS PRA QUÊ? Escalabilidade / Performance (toda informação exibida já pode estar obsoleta)

Slide 12

Slide 12 text

class UserController { public function create(Request $request): ResponseInterface { $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 13

Slide 13 text

AVISO! Estamos usando um ORM! (ida e vinda passam por ele)

Slide 14

Slide 14 text

E O BANCO DE DADOS?

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

CQRS é baseado em eventos (usar o que cada "storage" faz de melhor)

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

ESCRITA LEITURA

Slide 19

Slide 19 text

CONCEITO IMPORTANTE Command Handler (usado para a parte de escrita)

Slide 20

Slide 20 text

1 Command para 1 Handler RegisterUserCommand + RegisterUserHandler (command para dados + handler para lógica)

Slide 21

Slide 21 text

POST /users final class RegisterUserAction { private RegisterUserHandler $handler; public function __construct(RegisterUserHandler $handler) { $this->handler = $handler; } public function __invoke(RequestInterface $request): ResponseInterface { $userId = \Ramsey\Uuid\Uuid::uuid4(); $this->handler->handle( new RegisterUserCommand( $userId, $request->getAttribute('name'), $request->getAttribute('email') ) ) return new JsonResponse(['id' => $userId], 201); } }

Slide 22

Slide 22 text

COMMAND: PARA OS DADOS final class RegisterUserCommand { private string $id; private string $name; private string $email; public function __construct( string $id, string $name, string $email ) { $this->id = $id; $this->name = $name; $this->email = $email; } // getId(), getName(), getEmail() }

Slide 23

Slide 23 text

HANDLER: PARA A LÓGICA final class RegisterUserHandler { private UserRepositoryInterface $userRepository; public function __construct(UserRepositoryInterface $userRepository) { $this->userRepository = $userRepository; } public function handle(RegisterUserCommand $command): void { $this->userRepository->persist( new User( $command->getId(), $command->getName(), $command->getEmail() ) ); } }

Slide 24

Slide 24 text

EVENTOS NÃO TEM RETORNO (vou criar o usuário mas não vou retornar ele) CQRS: operações de escrita não possuem retorno

Slide 25

Slide 25 text

WTF? Como não vou retornar o usuário? (você não precisa, acredite!)

Slide 26

Slide 26 text

BASEADO EM EVENTOS Posso jogar um Handler na fila, por exemplo! CommandBus (retorno bem rápido pro usuário)

Slide 27

Slide 27 text

MAS E SE FALHAR? A fila vai processar quando der novamente (pode parar o banco e a fila irá ficar esperando)

Slide 28

Slide 28 text

Actor RegisterUserHandler Queue ID Data Processing

Slide 29

Slide 29 text

E A LEITURA? (QUERY?)

Slide 30

Slide 30 text

final class UserFinder { private Connection $connection; public function __construct(Connection $connection) { $this->connection = $connection; } public function find(int $id): array { $userData = $this->connection->findOne(['id' => $id]); if (!$userData) { throw UserNotFoundException::withId($id); } return $userData; } }

Slide 31

Slide 31 text

Connection? Não precisamos de Repository ou ORM! Dados puros (array) são suficientes para o retorno (JSON)

Slide 32

Slide 32 text

EVENT SOURCING TUDO É EVENTO (mudanças de estado são registradas como eventos)

Slide 33

Slide 33 text

CASO DE USO ChangeEmailHandler (altera o email do usuário)

Slide 34

Slide 34 text

id name email 1 Junior Grossi [email protected] $handler->handle( new ChangeEmailCommand( 'userId' => 1, 'newEmail' => '[email protected]', ) );

Slide 35

Slide 35 text

id name email 1 Junior Grossi [email protected]

Slide 36

Slide 36 text

PROBLEMA? Qual a mudança foi feita? O que causou a mudança? (não temos estes registros, infelizmente!)

Slide 37

Slide 37 text

Isto é Event Sourcing! ❤ [ { event: "UserWasRegistered", createdAt: "2019-06-08", payload: {id: 1, name: "Junior Grossi", email: "[email protected]"} }, { event: "EmailWasChanged", createdAt: "2019-06-09", payload: {id: 1, newEmail: "[email protected]"} } ]

Slide 38

Slide 38 text

POR QUE EVENT SOURCING? Controle das mudanças de estado Reescrever a história quando algo der errado (tomada de decisão como estratégia)

Slide 39

Slide 39 text

Todo evento é descrito no passado: OrderPaid (registro do passo a passo e dos dados dos eventos)

Slide 40

Slide 40 text

CASOS DE USO (EXEMPLOS) BUGFIXES: achar o ponto exato que houve o problema (e ver qual foi a mudança que causou o bug) Reescrever história do Banco de Dados (literalmente voltar no tempo para resolver algum problema)

Slide 41

Slide 41 text

composer require prooph/common prooph/event-sourcing https://github.com/prooph

Slide 42

Slide 42 text

CONCEITO IMPORTANTE Aggregates (usado para a parte de escrita)

Slide 43

Slide 43 text

AGGREGATES Representam estado de consistência Garantem que eu possa mudar de um estado para outro (validação a nível de regras de negócio) ShoppingCartAggregate

Slide 44

Slide 44 text

REGRAS DE NEGÓCIO "Um carrinho começa sempre vazio" "Um produto só pode ser adicionado ao carrinho se houver estoque" "A quantidade de um produto no carrinho não pode ser maior do que o estoque"

Slide 45

Slide 45 text

use Prooph\EventSourcing\AggregateRoot; final class ShoppingCartAggregate extends AggregateRoot { public static function startShoppingSession( ShoppingSession $shoppingSession, BasketId $basketId) { $self = new self(); $self->recordThat(ShoppingSessionStarted::occur($basketId->toString(), [ 'shopping_session' => $shoppingSession->toString() ])); return $self; } protected function aggregateId(): string { // TODO: Implement aggregateId() method. } protected function apply(AggregateChanged $event): void { // TODO: Implement apply() method. } }

Slide 46

Slide 46 text

Mais informações sobre Aggregates http://docs.getprooph.org/tutorial/event_sourcing_basics.html

Slide 47

Slide 47 text

RESUMÃO

Slide 48

Slide 48 text

CQRS Separar leitura de escrita Command + Handler = Event (sincronizar os bancos com base neste evento)

Slide 49

Slide 49 text

EVENT SOURCING Registrar cada mudança de estado (usando eventos) Events + Aggregates (registrar o evento no banco com o payload) (histórico de todas mudança de estado)

Slide 50

Slide 50 text

CONSIDERAÇÕES Não use CQRS + Event Sourcing no projeto todo (apenas onde você realmente precisa)

Slide 51

Slide 51 text

COMO COMEÇAR? Aplique CQRS primeiro! Command + Handler pattern (Event Sourcing possui uma curva de aprendizado maior)

Slide 52

Slide 52 text

OBRIGADO! AVALIE ESTA PALESTRA NO JOIND.IN ⭐ https://joind.in/talk/6352e http://twitter.com/junior_grossi