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

All aboard the Service Bus @ ZGPHP

robertbasic
February 15, 2018

All aboard the Service Bus @ ZGPHP

We deal with complicated and complex applications on a daily basis, codebases that are filled with classes that do too many things. One design pattern that can help us with this is CQRS, Command Query Responsibility Seggregation. The idea behind CQRS is to split our models in two - the Command for writing, and the Query for reading. Applying CQRS can lead us to a more maintainable code, code that follows the SOLID principles more closely.

At the heart of CQRS lies the Service Bus - a transport mechanism responsible for dispatching our command, event, and query messages to their destinations.

This talk will give an overview of the CQRS pattern and take a closer look at the different types of service buses - command, event, and query ones. Main takeaway will be practical information on why, when, and how to use them, with emphasis on their differences. We'll also take a look at some of the PHP libraries out there that help us work with service buses like Prooph Service Bus, Simple Bus, Tactician, to name a few.

robertbasic

February 15, 2018
Tweet

More Decks by robertbasic

Other Decks in Programming

Transcript

  1. Robert Bašić ~ ZGPHP #78 Same old story • An

    image gallery • Multiple currencies
  2. Robert Bašić ~ ZGPHP #78 Same old story • An

    image gallery • Multiple currencies • Coupons
  3. Robert Bašić ~ ZGPHP #78 Same old story • An

    image gallery • Multiple currencies • Coupons • Sales
  4. Robert Bašić ~ ZGPHP #78 Same old story • An

    image gallery • Multiple currencies • Coupons • Sales • Reviews
  5. Robert Bašić ~ ZGPHP #78 Same old story • An

    image gallery • Multiple currencies • Coupons • Sales • Reviews • With images
  6. Robert Bašić ~ ZGPHP #78 Same old story • An

    image gallery • Multiple currencies • Coupons • Sales • Reviews • With images • Old products
  7. Robert Bašić ~ ZGPHP #78 Same old story • An

    image gallery • Multiple currencies • Coupons • Sales • Reviews • With images • Old products • Warehousing
  8. Robert Bašić ~ ZGPHP #78 CQRS • Use cases •

    Separation of concerns • Improved readability
  9. Robert Bašić ~ ZGPHP #78 CQRS • Use cases •

    Separation of concerns • Improved readability • Improved performance
  10. Robert Bašić ~ ZGPHP #78 Before CQRS <?php namespace App\Service;

    class Product { public function update(array $product) { $presaveProduct = $this→getProductById($product['id']); if ($presaveProduct['sale'] == 1 && $presaveProduct['price'] != $product['price']) { throw new \Exception("Can't update price of products!"); } return $this->db->update('products', $product, ['id' => $product['id']]); } public function getProductById($productId) { /* ... */ } }
  11. Robert Bašić ~ ZGPHP #78 After CQRS (i) <?php namespace

    App\Product\Command; use App\Product; class UpdateProductPrice { public function __construct(string $newPrice, string $currency, Product\Product $product) { $this->newPrice = Product\Price::fromString($newPrice, $currency); $this->product = $product; } public function newPrice() { return $this->newPrice; } public function product() { return $this->product; } }
  12. Robert Bašić ~ ZGPHP #78 After CQRS (ii) <?php namespace

    App\Product\CommandHandler; use App\Product\Command; class UpdateProductPrice { public function handle(Command\UpdateProductPrice $command) { $product = $command->product(); $newPrice = $command->newPrice(); if ($product->onSale()) { throw new \Exception("Can't update price of products that are on sale!"); } $product->updatePrice($newPrice); $this->repository->save($product); } }
  13. Robert Bašić ~ ZGPHP #78 CQRS, the good • Smaller

    classes • Separated responsibilities
  14. Robert Bašić ~ ZGPHP #78 CQRS, the good • Smaller

    classes • Separated responsibilities • Faster writes and reads
  15. Robert Bašić ~ ZGPHP #78 CQRS, the good • Smaller

    classes • Separated responsibilities • Faster writes and reads • Easier database queries
  16. Robert Bašić ~ ZGPHP #78 CQRS, the bad • More

    classes • Translation • Complex syncing
  17. Robert Bašić ~ ZGPHP #78 CQRS, the bad • More

    classes • Translation • Complex syncing • Eventual consistency
  18. Robert Bašić ~ ZGPHP #78 Types of service buses •

    Message type • Command bus • Event bus
  19. Robert Bašić ~ ZGPHP #78 Types of service buses •

    Message type • Command bus • Event bus • Query bus
  20. Robert Bašić ~ ZGPHP #78 Service buses in PHP •

    Varying support for message types
  21. Robert Bašić ~ ZGPHP #78 Service buses in PHP •

    Varying support for message types • Tactician
  22. Robert Bašić ~ ZGPHP #78 Service buses in PHP •

    Varying support for message types • Tactician • SimpleBus
  23. Robert Bašić ~ ZGPHP #78 Service buses in PHP •

    Varying support for message types • Tactician • SimpleBus • Prooph Service Bus
  24. Robert Bašić ~ ZGPHP #78 Commands • Messages about user

    intention • CreateProduct, PutProductOnSale
  25. Robert Bašić ~ ZGPHP #78 Commands • Messages about user

    intention • CreateProduct, PutProductOnSale • Name reveals use case
  26. Robert Bašić ~ ZGPHP #78 Commands • Messages about user

    intention • CreateProduct, PutProductOnSale • Name reveals use case • One command, one action
  27. Robert Bašić ~ ZGPHP #78 Dispatching commands, HTTP <?php class

    UpdatePriceAction { public function __invoke(Request $request) { $product = $this->getProduct($request->get('productid')); $command = new UpdateProductPrice( $request->get('price'), $request->get('currency'), $product ); $this->commandBus->dispatch($command); } private function getProduct($productId): Model\Product }
  28. Robert Bašić ~ ZGPHP #78 Dispatching commands, CLI <?php class

    UpdatePriceCli { public function __invoke(Input $input) { $product = $this->getProduct($input->get('productid')); $command = new UpdateProductPrice( $input->get('price'), $input->get('currency'), $product ); $this->commandBus->dispatch($command); } private function getProduct($productId): Model\Product }
  29. Robert Bašić ~ ZGPHP #78 A command is always valid

    <?php namespace App\Product\Command; use App\Product; class UpdateProductPrice { public function __construct(string $newPrice, string $currency, Product\Product $product) { $this->newPrice = Product\Price::fromString($newPrice, $currency); $this->product = $product; } public function newPrice() { return $this->newPrice; } public function product() { return $this->product; } }
  30. Robert Bašić ~ ZGPHP #78 Command handlers <?php namespace App\Product\CommandHandler;

    use App\Product\Command; class UpdateProductPrice { public function handle(Command\UpdateProductPrice $command) { $product = $command->product(); $newPrice = $command->newPrice(); if ($product->onSale()) { throw new \Exception("Can't update price of products that are on sale!"); } $product->updatePrice($newPrice); $this->repository->save($product); } }
  31. Robert Bašić ~ ZGPHP #78 Command buses in PHP •

    Tactician, SimpleBus, Prooph Service Bus
  32. Robert Bašić ~ ZGPHP #78 Command buses in PHP •

    Tactician, SimpleBus, Prooph Service Bus • Differ in creation and configuration
  33. Robert Bašić ~ ZGPHP #78 Command buses in PHP •

    Tactician, SimpleBus, Prooph Service Bus • Differ in creation and configuration • Similar usage
  34. Robert Bašić ~ ZGPHP #78 Command buses in PHP •

    Tactician, SimpleBus, Prooph Service Bus • Differ in creation and configuration • Similar usage • Plugins, middlewares
  35. Robert Bašić ~ ZGPHP #78 Events • Messages about past

    events • After a command was handled
  36. Robert Bašić ~ ZGPHP #78 Events • Messages about past

    events • After a command was handled • ProductCreated, ProductPriceUpdated
  37. Robert Bašić ~ ZGPHP #78 Events • Messages about past

    events • After a command was handled • ProductCreated, ProductPriceUpdated • Once dispatched, can’t be stopped
  38. Robert Bašić ~ ZGPHP #78 Dispatching events <?php namespace App\Product\CommandHandler;

    use App\Product; class UpdateProductPrice { public function handle(Product\Command\UpdateProductPrice $command) { /** ... snip ... **/ $this->repository->save($product); $event = new Product\Event\ProductPriceUpdated( (int) $product->id(), (double) $oldPrice, (double) $newPrice ); $this->eventBus->dispatch($event); } }
  39. Robert Bašić ~ ZGPHP #78 An event is always valid

    <?php namespace App\Product\Event; class ProductPriceUpdated { public function __construct(int $productId, double $oldPrice, double $newPrice) { $this->productId = $productId; $this->oldPrice = $oldPrice; $this->newPrice = $newPrice; } public function productId() { return $this->productId; } public function oldPrice() { return $this->oldPrice; } public function newPrice() { return $this->newPrice; } }
  40. Robert Bašić ~ ZGPHP #78 Event listeners <?php namespace App\Product\EventListener;

    use App\Product; class NotifyAboutPriceDrop { public function handle(Product\Event\ProductPriceUpdated $event) { if ($event->oldPrice() <= $event->newPrice()) { return; } $product = $this→repository→get($productId); $command = new Product\Command\SendPriceDecreaseEmail($product); $this->commandBus->dispatch($command); } }
  41. Robert Bašić ~ ZGPHP #78 Event buses in PHP •

    SimpleBus, Prooph Service Bus • Differ in creation and configuration
  42. Robert Bašić ~ ZGPHP #78 Event buses in PHP •

    SimpleBus, Prooph Service Bus • Differ in creation and configuration • Similar usage
  43. Robert Bašić ~ ZGPHP #78 Event buses in PHP •

    SimpleBus, Prooph Service Bus • Differ in creation and configuration • Similar usage • Plugins, middlewares
  44. Robert Bašić ~ ZGPHP #78 Queries • Not the same

    as database queries • A query is a question
  45. Robert Bašić ~ ZGPHP #78 Queries • Not the same

    as database queries • A query is a question • LatestProductsCreated, ProductsOnSale
  46. Robert Bašić ~ ZGPHP #78 Queries • Not the same

    as database queries • A query is a question • LatestProductsCreated, ProductsOnSale • Answers from read models
  47. Robert Bašić ~ ZGPHP #78 Dispatching queries <?php class SalesReportAction

    { public function __invoke(Request $request) { $query = new ProductsBoughtInTimeframe( $request->get('from'), $request->get('to') ); $products = $this->queryBus->dispatch($query); // pass $products to template for displaying ... } }
  48. Robert Bašić ~ ZGPHP #78 A query is always valid

    <?php namespace App\Product\Query; class ProductsBoughtInTimeframe { public function __construct(string $from, string $to) { $this->from = new \DateTimeImmutable($from); $this->to = new \DatetTimeImmutable($to); } public function from(): \DatetTimeImmutable { return $this->from; } public function to(): \DatetTimeImmutable { return $this->to; } }
  49. Robert Bašić ~ ZGPHP #78 Query handlers <?php namespace App\Product\QueryHandler;

    use App\Product\Query; class ProductsBoughtInTimeframe { public function handle(Query\ProductsBoughtInTimeframe $query) { return $this->productsBoughtReadModel->fetch($query->from(), $query->to()); } }
  50. Robert Bašić ~ ZGPHP #78 Query buses in PHP •

    Prooph Service Bus • Plugins
  51. Robert Bašić ~ ZGPHP #78 Resources • CQRS Documents by

    Greg Young • SimpleBus by Matthias Noback • Prooph series by Robert Basic • perfi by Robert Basic