O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

Command Bus To Awesome Town

As presented at Dutch PHP Conference 2015, an introduction to command buses, how to implement your own in PHP and why they're both useful but unimportant.

  • Entre para ver os comentários

Command Bus To Awesome Town

  1. 1. Command Bus To DPC 2015 Awesome Town Ross Tuck
  2. 2. Command Bus To Awesome Town
  3. 3. This is a story...
  4. 4. ...about a refactoring.
  5. 5. $store->purchaseGame($gameId, $customerId);
  6. 6. Domain Model Service Controller View
  7. 7. $store->purchaseGame($gameId, $customerId);
  8. 8. class BUYBUYBUYController { public function buyAction(Request $request) { $form = $this->getForm(); $form->bindTo($request); if ($form->isTotallyValid()) { $store->purchaseGame($gameId, $customerId); return $this->redirect('thanks_for_money'); } return ['so_many_errors' => $form->getAllTheErrors()]; } }
  9. 9. class Store { public function purchaseGame($gameId, $customerId) { Assert::notNull($gameId); Assert::notNull($customerId); $this->security->allowsPurchase($customerId); $this->logging->debug('purchasing game'); try { $this->db->beginTransaction(); $purchase = new Purchase($gameId, $customerId); $this->repository->add($purchase); $this->db->commitTransaction(); } catch(Exception $e) { $this->db->rollbackTransaction(); throw $e; } } }
  10. 10. class Store { public function purchaseGame($gameId, $customerId) { $purchase = new Purchase($gameId, $customerId); $this->repository->add($purchase); } }
  11. 11. $store->purchaseGame($gameId, $customerId);
  12. 12. ->purchaseGame($gameId, $customerId); $store
  13. 13. purchaseGame($gameId, $customerId); $store->
  14. 14. purchaseGame $gameId, $customerId ; $store-> ( )
  15. 15. purchaseGame $gameId $customerId $store-> ( , );
  16. 16. purchaseGame $gameId $customerId Data
  17. 17. purchaseGame $gameId $customerId Intent Information
  18. 18. purchaseGame $gameId $customerId
  19. 19. PurchaseGame $gameId $customerId
  20. 20. PurchaseGame($gameId, $customerId);
  21. 21. new PurchaseGame($gameId, $customerId);
  22. 22. new PurchaseGame($gameId, $customerId); ->getGameId(); ->getCustomerId();
  23. 23. new PurchaseGame($gameId, $customerId);
  24. 24. Object
  25. 25. Message
  26. 26. People Paperwork→ Machines Messages→
  27. 27. Different types of messages
  28. 28. Command
  29. 29. Reserve Seat
  30. 30. Reserve Seat Schedule Appointment
  31. 31. new PurchaseGame($gameId, $customerId); Reserve Seat Schedule Appointment
  32. 32. Data Structure Why object?
  33. 33. +------------+--------------+ | Field | Type | +------------+--------------+ | id | varchar(60) | | indication | varchar(255) | | arrived | tinyint(1) | | firstName | varchar(255) | | lastName | varchar(255) | | birthDate | datetime | +------------+--------------+
  34. 34. struct purchase_game { int game_id; int customer_id; }
  35. 35. Map[Int, Int] Tuple[Int, Int]
  36. 36. [ 'game_id' => 42, 'customer_id' => 11 ]
  37. 37. class PurchaseGame { public $gameId; public $customerId; }
  38. 38. class PurchaseGame { private $gameId; private $customerId; public function __construct($gameId, $customerId) { $this->gameId = $gameId; $this->customerId = $customerId; } public function getGameId() { return $this->gameId; } public function getCustomerId() { return $this->customerId; } }
  39. 39. class PurchaseController { public function purchaseGameAction(Request $request) { $form = $this->createForm('purchase_game'); $form->bind($request); if ($form->isValid()) { $command = $form->getData(); } } }
  40. 40. We have intent...
  41. 41. ...but no ability to carry out.
  42. 42. Command Handlers
  43. 43. Handlers handle commands
  44. 44. 1:1 Command to Handler
  45. 45. class PurchaseGameHandler { public function handle(PurchaseGame $command) { Assert::notNull($command->getGameId()); Assert::notNull($command->getCustomerId()); $this->security->allowsPurchase($command->getCustomerId()); $this->logging->debug('purchasing game'); try { $this->db->beginTransaction(); $purchase = new Purchase( $command->getGameId(), $command->getCustomerId() ); $this->repository->add($purchase); $this->db->commitTransaction(); } catch(Exception $e) { $this->db->rollbackTransaction(); throw $e; } } }
  46. 46. class PurchaseGameHandler { public function handle(PurchaseGame $command) { $this->security->allowsPurchase($command->getCustomerId()); $this->logging->debug('purchasing game'); try { $this->db->beginTransaction(); $purchase = new Purchase( $command->getGameId(), $command->getCustomerId() ); $this->repository->add($purchase); $this->db->commitTransaction(); } catch(Exception $e) { $this->db->rollbackTransaction(); throw $e; } } }
  47. 47. Command + Handler
  48. 48. $handler->handle(new PurchaseGame($gameId, $customerId)); $store->purchaseGame($gameId, $customerId); “Extract To Message”
  49. 49. Command Message vs Command Pattern
  50. 50. $command = new PurchaseGame($gameId, $customerId); $handler->handle($command); Handler version
  51. 51. $command = new PurchaseGame($gameId, $customerId);
  52. 52. $command = new PurchaseGame($gameId, $customerId); $command->execute(); Classical version
  53. 53. $command = new PurchaseGame($gameId, $customerId, $repository, $db, $logger, $security); $command->execute(); Classical version
  54. 54. ● Game ID ● Customer ID Purchase THIS game Purchase ANY game ● Repository ● Security ● Logger ● DB
  55. 55. I generally advocate Handlers
  56. 56. Connecting the Dots
  57. 57. Command Handler
  58. 58. class PurchaseController { public function purchaseGameAction() { // form stuff lol $command = $form->getData(); $this->handler->handle($command); } } Controller Must be the RIGHT handler
  59. 59. Mailman
  60. 60. Enter the Bus
  61. 61. Command Bus
  62. 62. Handler Command Bus Command
  63. 63. Handler Command Bus Command
  64. 64. Handler Command Bus Command ♥♥ ♥ ♥ ♥
  65. 65. So, what is this command bus?
  66. 66. new CommandBus( [ PurchaseGame::class => $purchaseGameHandler, RegisterUser::class => $registerUserHandler ] );
  67. 67. $command = new PurchaseGame($gameId, $customerId); $commandBus->handle($command);
  68. 68. ● Over the Network ● In a queue ● Execute in process
  69. 69. class CommandBus { private $handlers = []; public function __construct($handlers) { $this->handlers = $handlers; } public function handle($command) { $name = get_class($command); if (!isset($this->handlers[$name])) { throw new Exception("No handler for $name"); } $this->handlers[$name]->handle($command); } }
  70. 70. Easier to Wire
  71. 71. Handler Freedom
  72. 72. It's just conventions
  73. 73. class CommandBus { private $handlers = []; public function __construct($handlers) { $this->handlers = $handlers; } public function handle($command) { $name = get_class($command); if (!isset($this->handlers[$name])) { throw new Exception("No handler for $name"); } $this->handlers[$name]->handle($command); } }
  74. 74. class CommandBus { private $handlers = []; public function __construct($handlers) { $this->handlers = $handlers; } public function handle($command) { $name = get_class($command); if (!isset($this->handlers[$name])) { throw new Exception("No handler for $name"); } $this->handlers[$name]->execute($command); } }
  75. 75. class CommandBus { private $handlers = []; public function __construct($handlers) { $this->handlers = $handlers; } public function handle($command) { $name = get_class($command); if (!isset($this->handlers[$name])) { throw new Exception("No handler for $name"); } $this->handlers[$name]->__invoke($command); } }
  76. 76. class CommandBus { private $handlers = []; public function __construct($handlers) { $this->handlers = $handlers; } public function handle($command) { $name = get_class($command); if (!isset($this->handlers[$name])) { throw new Exception("No handler for $name"); } $methodName = 'handle'.$name; $this->handlers[$name]->{$methodName}($command); } }
  77. 77. class CommandBus { private $handlers = []; public function __construct($handlers) { $this->handlers = $handlers; } public function handle($command) { $name = get_class($command); if (!isset($this->handlers[$name])) { throw new Exception("No handler for $name"); } $this->handlers[$name]->handle($command); } }
  78. 78. class CommandBus { private $container = []; public function __construct($container) { $this->container = $container; } public function handle($command) { $name = get_class($command)."Handler"; if (!$this->container->has($name)) { throw new Exception("No handler for $name"); } $this->container->get($name)->handle($command); } }
  79. 79. It's just conventions
  80. 80. Plugins
  81. 81. Command Bus Uniform Interface→
  82. 82. ->handle($command);
  83. 83. Decorator Pattern
  84. 84. Command Bus 3 Command Bus 2 Command Bus
  85. 85. Command Bus 1 Command Bus 3 Command Bus 2
  86. 86. Command Bus 3 Command Bus 2 Command Bus
  87. 87. Command Bus 3 Logging Command Bus Command Bus
  88. 88. Transaction Command Bus Logging Command Bus Command Bus
  89. 89. interface CommandBus { public function handle($command); }
  90. 90. class CommandBus { private $handlers = []; public function __construct($handlers) { $this->handlers = $handlers; } public function handle($command) { $name = get_class($command); if (!isset($this->handlers[$name])) { throw new Exception("No handler for $name"); } $this->handlers[$name]->handle($command); } }
  91. 91. class MyCommandBus { private $handlers = []; public function __construct($handlers) { $this->handlers = $handlers; } public function handle($command) { $name = get_class($command); if (!isset($this->handlers[$name])) { throw new Exception("No handler for $name"); } $this->handlers[$name]->handle($command); } }
  92. 92. class MyCommandBus implements CommandBus { private $handlers = []; public function __construct($handlers) { $this->handlers = $handlers; } public function handle($command) { $name = get_class($command); if (!isset($this->handlers[$name])) { throw new Exception("No handler for $name"); } $this->handlers[$name]->handle($command); } }
  93. 93. What to refactor out?
  94. 94. class PurchaseGameHandler { public function handle(PurchaseGame $command) { $this->security->allowsPurchase($command->getCustomerId()); $this->logging->debug('purchasing game'); try { $this->db->beginTransaction(); $purchase = new Purchase( $command->getGameId(), $command->getCustomerId() ); $this->repository->add($purchase); $this->db->commitTransaction(); } catch(Exception $e) { $this->db->rollbackTransaction(); throw $e; } } }
  95. 95. class LoggingDecorator implements CommandBus { private $logger; private $innerBus; public function __construct(Logger $logger, CommandBus $innerBus) { $this->logger = $logger; $this->innerBus = $innerBus; } public function handle($command) { $this->logger->debug('Handling command '.get_class($command)); $this->innerBus->handle($command); } }
  96. 96. class LoggingDecorator implements CommandBus { private $logger; private $innerBus; public function __construct(Logger $logger, CommandBus $innerBus) { $this->logger = $logger; $this->innerBus = $innerBus; } public function handle($command) { $this->innerBus->handle($command); $this->logger->debug('Executed command '.get_class($command)); } }
  97. 97. class LoggingDecorator implements CommandBus { private $logger; private $innerBus; public function __construct(Logger $logger, CommandBus $innerBus) { $this->logger = $logger; $this->innerBus = $innerBus; } public function handle($command) { $this->logger->debug('Handling command '.get_class($command)); $this->innerBus->handle($command); $this->logger->debug('Executed command '.get_class($command)); } }
  98. 98. $commandBus = new LoggingDecorator( $logger, new MyCommandBus(...) ); $commandBus->handle($command);
  99. 99. class PurchaseGameHandler { public function handle(PurchaseGame $command) { $this->security->allowsPurchase($command->getCustomerId()); $this->logging->debug('purchasing game'); try { $this->db->beginTransaction(); $purchase = new Purchase( $command->getGameId(), $command->getCustomerId() ); $this->repository->add($purchase); $this->db->commitTransaction(); } catch(Exception $e) { $this->db->rollbackTransaction(); throw $e; } } }
  100. 100. class PurchaseGameHandler { public function handle(PurchaseGame $command) { $this->security->allowsPurchase($command->getCustomerId()); try { $this->db->beginTransaction(); $purchase = new Purchase( $command->getGameId(), $command->getCustomerId() ); $this->repository->add($purchase); $this->db->commitTransaction(); } catch(Exception $e) { $this->db->rollbackTransaction(); throw $e; } } }
  101. 101. Ridiculously Powerful
  102. 102. Redonkulously Powerful
  103. 103. Why not Events?
  104. 104. Cohesion
  105. 105. Execution
  106. 106. class PurchaseGameHandler { public function handle(PurchaseGame $command) { $this->security->allowsPurchase($command->getCustomerId()); try { $this->db->beginTransaction(); $purchase = new Purchase( $command->getGameId(), $command->getCustomerId() ); $this->repository->add($purchase); $this->db->commitTransaction(); } catch(Exception $e) { $this->db->rollbackTransaction(); throw $e; } } }
  107. 107. Let's Decoratorize TM
  108. 108. class TransactionDecorator implements CommandBus { private $db; private $innerBus; public function __construct($db, $innerBus) { $this->db = $db; $this->innerBus = $innerBus; } public function handle($command) { try { $this->db->beginTransaction(); $this->innerBus->handle($command); $this->db->commitTransaction(); } catch (Exception $e) { $this->db->rollbackTransaction(); throw $e; } } }
  109. 109. The Handler?
  110. 110. class PurchaseGameHandler { public function handle(PurchaseGame $command) { $this->security->allowsPurchase($command->getCustomerId()); try { $this->db->beginTransaction(); $purchase = new Purchase( $command->getGameId(), $command->getCustomerId() ); $this->repository->add($purchase); $this->db->commitTransaction(); } catch(Exception $e) { $this->db->rollbackTransaction(); throw $e; } } }
  111. 111. class PurchaseGameHandler { public function handle(PurchaseGame $command) { $this->security->allowsPurchase($command->getCustomerId()); $purchase = new Purchase( $command->getGameId(), $command->getCustomerId() ); $this->repository->add($purchase); } }
  112. 112. ...Wait, all the Commands?
  113. 113. Limiting which decorators run
  114. 114. Marker Interface
  115. 115. $this->security->allowsPurchase($command->getCustomerId());
  116. 116. interface PurchaseCommand { public function getCustomerId(); }
  117. 117. class PurchaseGame { private $gameId; private $customerId; // constructors, getters, etc }
  118. 118. class PurchaseGame implements PurchaseCommand { private $gameId; private $customerId; // constructors, getters, etc }
  119. 119. class AllowedPurchaseDecorator implements CommandBus { private $security; private $innerBus; public function __construct($security, $innerBus) { $this->security = $security; $this->innerBus = $innerBus; } public function handle($command) { if ($command instanceof PurchaseCommand) { $this->security->allowPurchase($command->getCustomerId()); } $this->innerBus->handle($command); } }
  120. 120. class PurchaseGameHandler { public function handle(PurchaseGame $command) { $this->security->allowsPurchase($command->getCustomerId()); $purchase = new Purchase( $command->getGameId(), $command->getCustomerId() ); $this->repository->add($purchase); } }
  121. 121. class PurchaseGameHandler { public function handle(PurchaseGame $command) { $purchase = new Purchase( $command->getGameId(), $command->getCustomerId() ); $this->repository->add($purchase); } }
  122. 122. class PurchaseGameHandler { public function handle(PurchaseGame $command) { $purchase = new Purchase( $command->getGameId(), $command->getCustomerId() ); $this->repository->add($purchase); } } Stop cutting!
  123. 123. Beyond Decorators
  124. 124. new AllowedPurchaseDecorator( $security, new TransactionDecorator( $db, new LoggingDecorator( $logger, new MyCommandBus( [ PurchaseGame::class => $purchaseGameHandler ] ) ) ) )
  125. 125. Constructor Parameter Method
  126. 126. interface Middleware { public function handle($command, callable $next); }
  127. 127. class LoggingMiddleware implements Middleware { private $logger; public function __construct($logger) { $this->logger = $logger; } public function handle($command, callable $next) { $this->logger->debug('handling '.get_class($command)); next($command); } }
  128. 128. new CommandBus( [ new AllowedPurchaseMiddleware($security), new TransactionMiddleware($db), new LoggingMiddleware($logger), new CommandHandlerMiddleware($handlerMapping), ] );
  129. 129. 5 Things That I Have Seen
  130. 130. Thing #1 Handling Commands in Commands
  131. 131. Domain Model Service Controller View PurchaseGameSendConfirmation
  132. 132. If it needs to happen with the command… JUST CALL IT
  133. 133. If it needs to happen after the command… Wait for a domain event
  134. 134. Thing #2 Reading From Commands Or the lack thereof
  135. 135. “Command Bus should never return anything.”
  136. 136. Command Bus != CQRS
  137. 137. $commandBus->handle(new PurchaseGame($gameId, $customerId)); $store->purchaseGame($gameId, $customerId);
  138. 138. $commandBus->handle(new PurchaseGame($gameId, $customerId)); $id = $this ->domainEvents ->waitFor(GamePurchased::class) ->getId();
  139. 139. Thing #3 Reading with a Command Bus
  140. 140. $handler->handle(new PurchaseGame($gameId, $customerId)); $this->repository->findPurchaseById($id); $this->readService->findPurchaseVerification($id); $this->queryBus->find(new PurchaseById($id));
  141. 141. Thing #4 Command Buses Work Great in JS
  142. 142. commandBus.handle({ "command": "purchase_game", "game_id": gameId, "customer_id": customerId });
  143. 143. Thing #5 Single Endpoint “REST”
  144. 144. /commands
  145. 145. /purchase-game
  146. 146. The Meta of Command Buses
  147. 147. Why are there 50 of them?
  148. 148. μ
  149. 149. function createBus($handlers) { return function ($command) use ($handlers) { $handlers[get_class($command)]($command); }; }
  150. 150. $bus = createBus( [ FooCommand::class => $someClosure, BarCommand::class => $someOtherClosure ] ); $cmd = new FooCommand(); $bus($cmd);
  151. 151. But you should use my library.
  152. 152. Command Bus Libraries are useless
  153. 153. ...but the plugins aren't.
  154. 154. Me tactician-doctrine tactician-logger named commands locking
  155. 155. @sagikazarmark tactician-bernard tactician-command-events
  156. 156. @boekkooi tactician-amqp
  157. 157. @rdohms @xtrasmal @Richard_Tuin tactician-bundle
  158. 158. @GeeH @mike_kowalsky tactician-module
  159. 159. Prasetyo Wicaksono Eugene Terentev Jildert Miedema Nigel Greenway Frank de Jonge Ivan Habunek tactician-service-provider yii2-tactician laravel-tactician tactician-container tactician-awesome-advice tactician-pimple
  160. 160. Shared Interfaces
  161. 161. The Future of Tactician
  162. 162. ● Simplify Interfaces ● Metadata ● Tracking ● RAD
  163. 163. Domain Model Service Controller View
  164. 164. SimpleBus Broadway
  165. 165. Command Handler Bus Decorators executes connects extends
  166. 166. Command Handler
  167. 167. The End
  168. 168. Images ● http://bj-o23.deviantart.com/art/NOT-Rocket-Science-324795055 ● sambabulli.blogsport.de ● https://www.youtube.com/watch?v=XHa6LIUJTPw ● http://khongthe.com/wallpapers/animals/puppy-bow-227081.jpg ● https://www.flickr.com/photos/emptyhighway/7173454/sizes/l ● https://www.flickr.com/photos/jronaldlee/5566380424
  169. 169. domcode.org Ross Tuck @rosstuck joind.in/14219 PHPArchitectureTour.com

×