Upload
ross-tuck
View
1.528
Download
1
Embed Size (px)
Citation preview
Command Bus To
DPC 2015
Awesome Town
Ross Tuck
Command Bus To Awesome Town
This is a story...
...about a refactoring.
$store->purchaseGame($gameId, $customerId);
Domain Model
Service
Controller
View
$store->purchaseGame($gameId, $customerId);
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()]; }}
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; } }}
class Store{ public function purchaseGame($gameId, $customerId) {
$purchase = new Purchase($gameId, $customerId); $this->repository->add($purchase);
}}
$store->purchaseGame($gameId, $customerId);
->purchaseGame($gameId, $customerId);$store
purchaseGame($gameId, $customerId);$store->
purchaseGame $gameId, $customerId ;$store-> ( )
purchaseGame $gameId $customerId $store-> ( , );
purchaseGame $gameId $customerId
Data
purchaseGame $gameId $customerId
Intent
Information
purchaseGame $gameId $customerId
PurchaseGame $gameId $customerId
PurchaseGame($gameId, $customerId);
new PurchaseGame($gameId, $customerId);
new PurchaseGame($gameId, $customerId);->getGameId();->getCustomerId();
new PurchaseGame($gameId, $customerId);
Object
Message
People Paperwork →Machines Messages→
Different types of messages
Command
Reserve Seat
Reserve SeatSchedule Appointment
new PurchaseGame($gameId, $customerId);
Reserve SeatSchedule Appointment
Data StructureWhy object?
+------------+--------------+| Field | Type |+------------+--------------+| id | varchar(60) || indication | varchar(255) || arrived | tinyint(1) || firstName | varchar(255) || lastName | varchar(255) || birthDate | datetime |+------------+--------------+
struct purchase_game { int game_id; int customer_id;}
Map[Int, Int]Tuple[Int, Int]
[ 'game_id' => 42, 'customer_id' => 11]
class PurchaseGame{ public $gameId; public $customerId;}
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; }}
class PurchaseController{ public function purchaseGameAction(Request $request) { $form = $this->createForm('purchase_game'); $form->bind($request);
if ($form->isValid()) { $command = $form->getData(); } }}
We have intent...
...but no ability to carry out.
Command Handlers
Handlers handle commands
1:1 Command to Handler
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; } }}
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; } }}
Command + Handler
$handler->handle(new PurchaseGame($gameId, $customerId));
$store->purchaseGame($gameId, $customerId);
“Extract To Message”
Command Message vs Command Pattern
$command = new PurchaseGame($gameId, $customerId);
$handler->handle($command);
Handler version
$command = new PurchaseGame($gameId, $customerId);
$command = new PurchaseGame($gameId, $customerId);
$command->execute();
Classical version
$command = new PurchaseGame($gameId, $customerId, $repository, $db, $logger, $security);$command->execute();
Classical version
● Game ID● Customer ID
Purchase THIS game Purchase ANY game● Repository● Security● Logger● DB
I generally advocate Handlers
Connecting the Dots
Command Handler
class PurchaseController{ public function purchaseGameAction() { // form stuff lol
$command = $form->getData(); $this->handler->handle($command); }}
Controller
Must be the RIGHT handler
Mailman
Enter the Bus
Command Bus
Handler
Command Bus
Command
Handler
Command Bus
Command
Handler
Command Bus
Command
♥♥♥
♥♥
So, what is this command bus?
new CommandBus( [ PurchaseGame::class => $purchaseGameHandler, RegisterUser::class => $registerUserHandler ]);
$command = new PurchaseGame($gameId, $customerId);$commandBus->handle($command);
● Over the Network● In a queue● Execute in process
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); }}
Easier to Wire
Handler Freedom
It's just conventions
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); }}
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); }}
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); }}
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); }}
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); }}
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); }}
It's just conventions
Plugins
Command Bus Uniform Interface→
->handle($command);
Decorator Pattern
Command Bus 3
Command Bus 2
Command Bus
Command Bus 1
Command B
us 3
Command B
us 2
Command Bus 3
Command Bus 2
Command Bus
Command Bus 3
Logging Command Bus
Command Bus
Transaction Command Bus
Logging Command Bus
Command Bus
interface CommandBus{ public function handle($command);}
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); }}
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); }}
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); }}
What to refactor out?
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; } }}
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);
}
}
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)); }
}
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)); }
}
$commandBus = new LoggingDecorator( $logger, new MyCommandBus(...));
$commandBus->handle($command);
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; } }}
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; } }}
Ridiculously Powerful
Redonkulously Powerful
Why not Events?
Cohesion
Execution
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; } }}
Let's DecoratorizeTM
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; } }}
The Handler?
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; } }}
class PurchaseGameHandler{ public function handle(PurchaseGame $command) {
$this->security->allowsPurchase($command->getCustomerId());
$purchase = new Purchase( $command->getGameId(), $command->getCustomerId() ); $this->repository->add($purchase);
}}
...Wait, all the Commands?
Limiting which decorators run
Marker Interface
$this->security->allowsPurchase($command->getCustomerId());
interface PurchaseCommand{ public function getCustomerId();}
class PurchaseGame{ private $gameId; private $customerId;
// constructors, getters, etc}
class PurchaseGame implements PurchaseCommand{ private $gameId; private $customerId;
// constructors, getters, etc}
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); }}
class PurchaseGameHandler{ public function handle(PurchaseGame $command) {
$this->security->allowsPurchase($command->getCustomerId());
$purchase = new Purchase( $command->getGameId(), $command->getCustomerId() ); $this->repository->add($purchase);
}}
class PurchaseGameHandler{ public function handle(PurchaseGame $command) {
$purchase = new Purchase( $command->getGameId(), $command->getCustomerId() ); $this->repository->add($purchase);
}}
class PurchaseGameHandler{ public function handle(PurchaseGame $command) {
$purchase = new Purchase( $command->getGameId(), $command->getCustomerId() ); $this->repository->add($purchase);
}}
Stop cutting!
Beyond Decorators
new AllowedPurchaseDecorator( $security, new TransactionDecorator( $db, new LoggingDecorator( $logger, new MyCommandBus( [ PurchaseGame::class => $purchaseGameHandler ] ) ) ))
Constructor Parameter
Method
interface Middleware { public function handle($command, callable $next);}
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); }}
new CommandBus( [ new AllowedPurchaseMiddleware($security), new TransactionMiddleware($db), new LoggingMiddleware($logger), new CommandHandlerMiddleware($handlerMapping), ] );
5 Things That I Have Seen
Thing #1Handling Commands in Commands
Domain Model
Service
Controller
View
PurchaseGameSendConfirmation
If it needs to happen with the command…JUST CALL IT
If it needs to happen after the command…Wait for a domain event
Thing #2Reading From Commands
Or the lack thereof
“Command Bus should never return anything.”
Command Bus != CQRS
$commandBus->handle(new PurchaseGame($gameId, $customerId));
$store->purchaseGame($gameId, $customerId);
$commandBus->handle(new PurchaseGame($gameId, $customerId));
$id = $this ->domainEvents ->waitFor(GamePurchased::class) ->getId();
Thing #3Reading with a Command Bus
$handler->handle(new PurchaseGame($gameId, $customerId));
$this->repository->findPurchaseById($id);$this->readService->findPurchaseVerification($id);$this->queryBus->find(new PurchaseById($id));
Thing #4Command Buses Work Great in JS
commandBus.handle({ "command": "purchase_game", "game_id": gameId, "customer_id": customerId});
Thing #5Single Endpoint “REST”
/commands
/purchase-game
The Meta of Command Buses
Why are there 50 of them?
μ
function createBus($handlers) { return function ($command) use ($handlers) { $handlers[get_class($command)]($command); };}
$bus = createBus( [ FooCommand::class => $someClosure, BarCommand::class => $someOtherClosure ]);
$cmd = new FooCommand();$bus($cmd);
But you should use my library.
Command Bus Libraries are useless
...but the plugins aren't.
Me
tactician-doctrinetactician-loggernamed commandslocking
@sagikazarmarktactician-bernardtactician-command-events
@boekkooi tactician-amqp
@rdohms@xtrasmal@Richard_Tuin
tactician-bundle
@GeeH@mike_kowalsky
tactician-module
Prasetyo WicaksonoEugene TerentevJildert MiedemaNigel GreenwayFrank de JongeIvan Habunek
tactician-service-provideryii2-tacticianlaravel-tacticiantactician-containertactician-awesome-advicetactician-pimple
Shared Interfaces
The Future of Tactician
● Simplify Interfaces● Metadata● Tracking● RAD
Domain Model
Service
Controller
View
SimpleBusBroadway
Command
Handler
Bus
Decorators
executes
connects extends
Command Handler
The End
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
domcode.org
Ross Tuck@rosstuck
joind.in/14219
PHPArchitectureTour.com