Command Bus To Awesome Town

Post on 11-Aug-2015

1,528 views 1 download

transcript

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