$30 off During Our Annual Pro Sale. View Details »

All aboard the Service Bus

All aboard the Service Bus

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

January 23, 2018
Tweet

More Decks by robertbasic

Other Decks in Programming

Transcript

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

    View Slide

  2. Robert Bašić ~ PHPSrbija #29
    A new project appears

    View Slide

  3. Robert Bašić ~ PHPSrbija #29
    Ready to build greatness

    View Slide

  4. Robert Bašić ~ PHPSrbija #29
    Your creation

    View Slide

  5. Robert Bašić ~ PHPSrbija #29
    // todo finish this

    View Slide

  6. Robert Bašić ~ PHPSrbija #29
    // refactor me later

    View Slide

  7. Robert Bašić ~ PHPSrbija #29
    // dirty hack to work around...

    View Slide

  8. Robert Bašić ~ PHPSrbija #29
    Same old story

    View Slide

  9. Robert Bašić ~ PHPSrbija #29
    Same old story
    ● An image gallery

    View Slide

  10. Robert Bašić ~ PHPSrbija #29
    Same old story
    ● An image gallery
    ● Multiple currencies

    View Slide

  11. Robert Bašić ~ PHPSrbija #29
    Same old story
    ● An image gallery
    ● Multiple currencies
    ● Coupons

    View Slide

  12. Robert Bašić ~ PHPSrbija #29
    Same old story
    ● An image gallery
    ● Multiple currencies
    ● Coupons
    ● Sales

    View Slide

  13. Robert Bašić ~ PHPSrbija #29
    Same old story
    ● An image gallery
    ● Multiple currencies
    ● Coupons
    ● Sales
    ● Reviews

    View Slide

  14. Robert Bašić ~ PHPSrbija #29
    Same old story
    ● An image gallery
    ● Multiple currencies
    ● Coupons
    ● Sales
    ● Reviews
    ● With images

    View Slide

  15. Robert Bašić ~ PHPSrbija #29
    Same old story
    ● An image gallery
    ● Multiple currencies
    ● Coupons
    ● Sales
    ● Reviews
    ● With images
    ● Old products

    View Slide

  16. Robert Bašić ~ PHPSrbija #29
    Same old story
    ● An image gallery
    ● Multiple currencies
    ● Coupons
    ● Sales
    ● Reviews
    ● With images
    ● Old products
    ● Warehousing

    View Slide

  17. Robert Bašić ~ PHPSrbija #29
    Been there, done that

    View Slide

  18. Robert Bašić ~ PHPSrbija #29
    Command Query Responsibility
    Segregation

    View Slide

  19. Robert Bašić ~ PHPSrbija #29
    CQRS

    View Slide

  20. Robert Bašić ~ PHPSrbija #29
    CQRS

    View Slide

  21. Robert Bašić ~ PHPSrbija #29
    CQRS
    ● Use cases

    View Slide

  22. Robert Bašić ~ PHPSrbija #29
    CQRS
    ● Use cases
    ● Separation of concerns

    View Slide

  23. Robert Bašić ~ PHPSrbija #29
    CQRS
    ● Use cases
    ● Separation of concerns
    ● Improved readability

    View Slide

  24. Robert Bašić ~ PHPSrbija #29
    CQRS
    ● Use cases
    ● Separation of concerns
    ● Improved readability
    ● Improved performance

    View Slide

  25. Robert Bašić ~ PHPSrbija #29
    Before CQRS
    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) { /* ... */ }
    }

    View Slide

  26. Robert Bašić ~ PHPSrbija #29
    After CQRS (i)
    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; }
    }

    View Slide

  27. Robert Bašić ~ PHPSrbija #29
    After CQRS (ii)
    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);
    }
    }

    View Slide

  28. Robert Bašić ~ PHPSrbija #29
    CQRS, the good

    View Slide

  29. Robert Bašić ~ PHPSrbija #29
    CQRS, the good
    ● Smaller classes

    View Slide

  30. Robert Bašić ~ PHPSrbija #29
    CQRS, the good
    ● Smaller classes
    ● Separated responsibilities

    View Slide

  31. Robert Bašić ~ PHPSrbija #29
    CQRS, the good
    ● Smaller classes
    ● Separated responsibilities
    ● Faster writes and reads

    View Slide

  32. Robert Bašić ~ PHPSrbija #29
    CQRS, the good
    ● Smaller classes
    ● Separated responsibilities
    ● Faster writes and reads
    ● Easier database queries

    View Slide

  33. Robert Bašić ~ PHPSrbija #29
    CQRS, the bad

    View Slide

  34. Robert Bašić ~ PHPSrbija #29
    CQRS, the bad
    ● More classes

    View Slide

  35. Robert Bašić ~ PHPSrbija #29
    CQRS, the bad
    ● More classes
    ● Translation

    View Slide

  36. Robert Bašić ~ PHPSrbija #29
    CQRS, the bad
    ● More classes
    ● Translation
    ● Complex syncing

    View Slide

  37. Robert Bašić ~ PHPSrbija #29
    CQRS, the bad
    ● More classes
    ● Translation
    ● Complex syncing
    ● Eventual consistency

    View Slide

  38. Robert Bašić ~ PHPSrbija #29
    Service Bus

    View Slide

  39. Robert Bašić ~ PHPSrbija #29
    What is a service bus?

    View Slide

  40. Robert Bašić ~ PHPSrbija #29
    Types of service buses

    View Slide

  41. Robert Bašić ~ PHPSrbija #29
    Types of service buses
    ● Message type

    View Slide

  42. Robert Bašić ~ PHPSrbija #29
    Types of service buses
    ● Message type
    ● Command bus

    View Slide

  43. Robert Bašić ~ PHPSrbija #29
    Types of service buses
    ● Message type
    ● Command bus
    ● Event bus

    View Slide

  44. Robert Bašić ~ PHPSrbija #29
    Types of service buses
    ● Message type
    ● Command bus
    ● Event bus
    ● Query bus

    View Slide

  45. Robert Bašić ~ PHPSrbija #29
    Service buses in PHP

    View Slide

  46. Robert Bašić ~ PHPSrbija #29
    Service buses in PHP
    ● Varying support for message types

    View Slide

  47. Robert Bašić ~ PHPSrbija #29
    Service buses in PHP
    ● Varying support for message types
    ● Tactician

    View Slide

  48. Robert Bašić ~ PHPSrbija #29
    Service buses in PHP
    ● Varying support for message types
    ● Tactician
    ● SimpleBus

    View Slide

  49. Robert Bašić ~ PHPSrbija #29
    Service buses in PHP
    ● Varying support for message types
    ● Tactician
    ● SimpleBus
    ● Prooph Service Bus

    View Slide

  50. Robert Bašić ~ PHPSrbija #29
    Command Bus

    View Slide

  51. Robert Bašić ~ PHPSrbija #29
    Command Bus

    View Slide

  52. Robert Bašić ~ PHPSrbija #29
    Commands

    View Slide

  53. Robert Bašić ~ PHPSrbija #29
    Commands
    ● Messages about user intention

    View Slide

  54. Robert Bašić ~ PHPSrbija #29
    Commands
    ● Messages about user intention
    ● CreateProduct, PutProductOnSale

    View Slide

  55. Robert Bašić ~ PHPSrbija #29
    Commands
    ● Messages about user intention
    ● CreateProduct, PutProductOnSale
    ● Name reveals use case

    View Slide

  56. Robert Bašić ~ PHPSrbija #29
    Commands
    ● Messages about user intention
    ● CreateProduct, PutProductOnSale
    ● Name reveals use case
    ● One command, one action

    View Slide

  57. Robert Bašić ~ PHPSrbija #29
    Dispatching commands, HTTP
    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
    }

    View Slide

  58. Robert Bašić ~ PHPSrbija #29
    Dispatching commands, CLI
    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
    }

    View Slide

  59. Robert Bašić ~ PHPSrbija #29
    A command is always valid
    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; }
    }

    View Slide

  60. Robert Bašić ~ PHPSrbija #29
    Command handlers
    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);
    }
    }

    View Slide

  61. Robert Bašić ~ PHPSrbija #29
    Command buses in PHP

    View Slide

  62. Robert Bašić ~ PHPSrbija #29
    Command buses in PHP
    ● Tactician, SimpleBus, Prooph Service Bus

    View Slide

  63. Robert Bašić ~ PHPSrbija #29
    Command buses in PHP
    ● Tactician, SimpleBus, Prooph Service Bus
    ● Differ in creation and configuration

    View Slide

  64. Robert Bašić ~ PHPSrbija #29
    Command buses in PHP
    ● Tactician, SimpleBus, Prooph Service Bus
    ● Differ in creation and configuration
    ● Similar usage

    View Slide

  65. Robert Bašić ~ PHPSrbija #29
    Command buses in PHP
    ● Tactician, SimpleBus, Prooph Service Bus
    ● Differ in creation and configuration
    ● Similar usage
    ● Plugins, middlewares

    View Slide

  66. Robert Bašić ~ PHPSrbija #29
    Event Bus

    View Slide

  67. Robert Bašić ~ PHPSrbija #29
    Event Bus

    View Slide

  68. Robert Bašić ~ PHPSrbija #29
    Events

    View Slide

  69. Robert Bašić ~ PHPSrbija #29
    Events
    ● Messages about past events

    View Slide

  70. Robert Bašić ~ PHPSrbija #29
    Events
    ● Messages about past events
    ● After a command was handled

    View Slide

  71. Robert Bašić ~ PHPSrbija #29
    Events
    ● Messages about past events
    ● After a command was handled
    ● ProductCreated, ProductPriceUpdated

    View Slide

  72. Robert Bašić ~ PHPSrbija #29
    Events
    ● Messages about past events
    ● After a command was handled
    ● ProductCreated, ProductPriceUpdated
    ● Once dispatched, can’t be stopped

    View Slide

  73. Robert Bašić ~ PHPSrbija #29
    Dispatching events
    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);
    }
    }

    View Slide

  74. Robert Bašić ~ PHPSrbija #29
    An event is always valid
    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; }
    }

    View Slide

  75. Robert Bašić ~ PHPSrbija #29
    Event listeners
    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);
    }
    }

    View Slide

  76. Robert Bašić ~ PHPSrbija #29
    Event buses in PHP

    View Slide

  77. Robert Bašić ~ PHPSrbija #29
    Event buses in PHP
    ● SimpleBus, Prooph Service Bus

    View Slide

  78. Robert Bašić ~ PHPSrbija #29
    Event buses in PHP
    ● SimpleBus, Prooph Service Bus
    ● Differ in creation and configuration

    View Slide

  79. Robert Bašić ~ PHPSrbija #29
    Event buses in PHP
    ● SimpleBus, Prooph Service Bus
    ● Differ in creation and configuration
    ● Similar usage

    View Slide

  80. Robert Bašić ~ PHPSrbija #29
    Event buses in PHP
    ● SimpleBus, Prooph Service Bus
    ● Differ in creation and configuration
    ● Similar usage
    ● Plugins, middlewares

    View Slide

  81. Robert Bašić ~ PHPSrbija #29
    Query Bus

    View Slide

  82. Robert Bašić ~ PHPSrbija #29
    Query Bus

    View Slide

  83. Robert Bašić ~ PHPSrbija #29
    Queries

    View Slide

  84. Robert Bašić ~ PHPSrbija #29
    Queries
    ● Not the same as database queries

    View Slide

  85. Robert Bašić ~ PHPSrbija #29
    Queries
    ● Not the same as database queries
    ● A query is a question

    View Slide

  86. Robert Bašić ~ PHPSrbija #29
    Queries
    ● Not the same as database queries
    ● A query is a question
    ● LatestProductsCreated, ProductsOnSale

    View Slide

  87. Robert Bašić ~ PHPSrbija #29
    Queries
    ● Not the same as database queries
    ● A query is a question
    ● LatestProductsCreated, ProductsOnSale
    ● Answers from read models

    View Slide

  88. Robert Bašić ~ PHPSrbija #29
    Dispatching queries
    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 ...
    }
    }

    View Slide

  89. Robert Bašić ~ PHPSrbija #29
    A query is always valid
    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;
    }
    }

    View Slide

  90. Robert Bašić ~ PHPSrbija #29
    Query handlers
    namespace App\Product\QueryHandler;
    use App\Product\Query;
    class ProductsBoughtInTimeframe {
    public function handle(Query\ProductsBoughtInTimeframe $query) {
    return $this->productsBoughtReadModel->fetch($query->from(), $query->to());
    }
    }

    View Slide

  91. Robert Bašić ~ PHPSrbija #29
    Query buses in PHP

    View Slide

  92. Robert Bašić ~ PHPSrbija #29
    Query buses in PHP
    ● Prooph Service Bus

    View Slide

  93. Robert Bašić ~ PHPSrbija #29
    Query buses in PHP
    ● Prooph Service Bus
    ● Plugins

    View Slide

  94. Robert Bašić ~ PHPSrbija #29
    Ready for the next project!

    View Slide

  95. Robert Bašić ~ PHPSrbija #29
    Resources
    ● CQRS Documents by Greg Young
    ● SimpleBus by Matthias Noback
    ● Prooph series by Robert Basic
    ● perfi by Robert Basic

    View Slide

  96. Robert Bašić ~ PHPSrbija #29
    Q&A
    Robert Bašić
    https://robertbasic.com/
    @robertbasic

    View Slide

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

    View Slide