All aboard the Service Bus @ ZGPHP

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.

6d9ae403ee630c0e51ec79105a3b7af1?s=128

robertbasic

February 15, 2018
Tweet

Transcript

  1. Robert Bašić https://robertbasic.com/ @robertbasic All aboard the Service Bus

  2. Robert Bašić ~ ZGPHP #78 A new project appears

  3. Robert Bašić ~ ZGPHP #78 Ready to build greatness

  4. Robert Bašić ~ ZGPHP #78 Your creation

  5. Robert Bašić ~ ZGPHP #78 // todo finish this

  6. Robert Bašić ~ ZGPHP #78 // refactor me later

  7. Robert Bašić ~ ZGPHP #78 // dirty hack to work

    around...
  8. Robert Bašić ~ ZGPHP #78 Same old story

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

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

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

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

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

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

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

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

    image gallery • Multiple currencies • Coupons • Sales • Reviews • With images • Old products • Warehousing
  17. Robert Bašić ~ ZGPHP #78 Been there, done that

  18. Robert Bašić ~ ZGPHP #78 Command Query Responsibility Segregation

  19. Robert Bašić ~ ZGPHP #78 CQRS

  20. Robert Bašić ~ ZGPHP #78 CQRS

  21. Robert Bašić ~ ZGPHP #78 CQRS • Use cases

  22. Robert Bašić ~ ZGPHP #78 CQRS • Use cases •

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

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

    Separation of concerns • Improved readability • Improved performance
  25. 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) { /* ... */ } }
  26. 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; } }
  27. 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); } }
  28. Robert Bašić ~ ZGPHP #78 CQRS, the good

  29. Robert Bašić ~ ZGPHP #78 CQRS, the good • Smaller

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

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

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

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

  34. Robert Bašić ~ ZGPHP #78 CQRS, the bad • More

    classes
  35. Robert Bašić ~ ZGPHP #78 CQRS, the bad • More

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

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

    classes • Translation • Complex syncing • Eventual consistency
  38. Robert Bašić ~ ZGPHP #78 Service Bus

  39. Robert Bašić ~ ZGPHP #78 What is a service bus?

  40. Robert Bašić ~ ZGPHP #78 Types of service buses

  41. Robert Bašić ~ ZGPHP #78 Types of service buses •

    Message type
  42. Robert Bašić ~ ZGPHP #78 Types of service buses •

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

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

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

  46. Robert Bašić ~ ZGPHP #78 Service buses in PHP •

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

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

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

    Varying support for message types • Tactician • SimpleBus • Prooph Service Bus
  50. Robert Bašić ~ ZGPHP #78 Command Bus

  51. Robert Bašić ~ ZGPHP #78 Command Bus

  52. Robert Bašić ~ ZGPHP #78 Commands

  53. Robert Bašić ~ ZGPHP #78 Commands • Messages about user

    intention
  54. Robert Bašić ~ ZGPHP #78 Commands • Messages about user

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

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

    intention • CreateProduct, PutProductOnSale • Name reveals use case • One command, one action
  57. 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 }
  58. 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 }
  59. 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; } }
  60. 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); } }
  61. Robert Bašić ~ ZGPHP #78 Command buses in PHP

  62. Robert Bašić ~ ZGPHP #78 Command buses in PHP •

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

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

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

    Tactician, SimpleBus, Prooph Service Bus • Differ in creation and configuration • Similar usage • Plugins, middlewares
  66. Robert Bašić ~ ZGPHP #78 Event Bus

  67. Robert Bašić ~ ZGPHP #78 Event Bus

  68. Robert Bašić ~ ZGPHP #78 Events

  69. Robert Bašić ~ ZGPHP #78 Events • Messages about past

    events
  70. Robert Bašić ~ ZGPHP #78 Events • Messages about past

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

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

    events • After a command was handled • ProductCreated, ProductPriceUpdated • Once dispatched, can’t be stopped
  73. 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); } }
  74. 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; } }
  75. 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); } }
  76. Robert Bašić ~ ZGPHP #78 Event buses in PHP

  77. Robert Bašić ~ ZGPHP #78 Event buses in PHP •

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

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

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

    SimpleBus, Prooph Service Bus • Differ in creation and configuration • Similar usage • Plugins, middlewares
  81. Robert Bašić ~ ZGPHP #78 Query Bus

  82. Robert Bašić ~ ZGPHP #78 Query Bus

  83. Robert Bašić ~ ZGPHP #78 Queries

  84. Robert Bašić ~ ZGPHP #78 Queries • Not the same

    as database queries
  85. Robert Bašić ~ ZGPHP #78 Queries • Not the same

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

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

    as database queries • A query is a question • LatestProductsCreated, ProductsOnSale • Answers from read models
  88. 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 ... } }
  89. 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; } }
  90. 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()); } }
  91. Robert Bašić ~ ZGPHP #78 Query buses in PHP

  92. Robert Bašić ~ ZGPHP #78 Query buses in PHP •

    Prooph Service Bus
  93. Robert Bašić ~ ZGPHP #78 Query buses in PHP •

    Prooph Service Bus • Plugins
  94. Robert Bašić ~ ZGPHP #78 Ready for the next project!

  95. Robert Bašić ~ ZGPHP #78 Resources • CQRS Documents by

    Greg Young • SimpleBus by Matthias Noback • Prooph series by Robert Basic • perfi by Robert Basic
  96. Robert Bašić ~ ZGPHP #78 Q&A Robert Bašić https://robertbasic.com/ @robertbasic

  97. Robert Bašić ~ ZGPHP #78 Images used • http://www.globalgeeknews.com/2011/12/22/the-life-of-a-so ftware-engineer-comic/