Wrangle Cross-cutting Concerns with Event Driven Development

Wrangle Cross-cutting Concerns with Event Driven Development

Event driven programming is becoming essential to many applications and frameworks that can be utilized to make your application more flexible and “plugin” ready.

Learn how to effectively use events in applications to reduce code complexity of cross-cutting concerns, how various frameworks implement events and make them available to the developer, and the benefits and drawbacks of utilizing aspect oriented development with real world examples.

We will also look at many popular frameworks (Symfony2, CakePHP, Zend, etc) to see how their event architecture is implemented at a bird’s-eye view and how developers can take advantage, including a demo using Symfony2’s dispatcher to illustrate the benefits of event driven design.

53c40ddf10f397ab4494fc55078125dc?s=128

Chris Saylor

October 05, 2013
Tweet

Transcript

  1. 3.

    A little about Zumba • 180 Countries • 15 million

    class participants • 60 million service requests per month
  2. 5.

    What are cross-cutting concerns? Cross-cutting concerns are parts of a

    program that rely on or must affect many other parts of the system.
  3. 6.

    Cross-cutting examples • Logging • Caching • Product feature interaction

    • Monitoring • Record audit trail • State Management
  4. 8.

    class Cart { public function addItem(Item $item) { $this->cart->add($item); if

    ($this->promo->isApplicable($this->cart, $item)) { $this->promo->applyItemPromo($item); } $this->cart->save(); } }
  5. 9.

    class Cart { public function addItem(Item $item) { if (!$this->inventory->isAvailable($item))

    { throw new ItemUnavailable(); } $this->cart->add($item); if ($this->promo->isApplicable($this->cart, $item)) { $this->promo->applyItemPromo($item); } $this->cart->save(); } }
  6. 10.

    class Cart { public function addItem(Item $item) { if (!$this->inventory->isAvailable($item))

    { throw new ItemUnavailable(); } $this->cart->add($item); if ($this->promo->isApplicable($this->cart, $item)) { $this->promo->applyItemPromo($item); } $this->inventory->reduceEphemeral($item); $this->cart->save(); } }
  7. 11.

    class Cart { public function addItem(Item $item) { if (!$this->inventory->isAvailable($item))

    { throw new ItemUnavailable(); } if (!$this->catalog->isUserAllowed($this->cart, $item)) { throw new ItemNotAllowed(); } $this->cart->add($item); if ($this->promo->isApplicable($this->cart, $item)) { $this->promo->applyItemPromo($item); } $this->inventory->reduceEphemeral($item); $this->cart->save(); } }
  8. 13.

    Cyclomatic Complexity A measure of the complexity by the number

    of linearly independent paths through the program. M = E - N + 2P E = the number of edges of the graph N = the number of nodes of the graph P = The number of connected components
  9. 15.
  10. 17.

    class Cart { public function addItem(Item $item) { if (!$this->inventory->isAvailable($item))

    { throw new ItemUnavailable(); } if (!$this->catalog->isUserAllowed($this->cart, $item)) { throw new ItemNotAllowed(); } $this->cart->add($item); if ($this->promo->isApplicable($this->cart, $item)) { $this->promo->applyItemPromo($item); } $this->inventory->reduceEphemeral($item); $this->cart->save(); } }
  11. 18.
  12. 19.

    13 - 11 + (2 * 1) = 4 Four

    times as much complexity!
  13. 20.

    Risks of cross-cutting concerns • Coupling systems too tightly. •

    Lots of code duplication. • Brittle Test Cases.
  14. 23.

    class Cart { public function addItem(Item $item) { if (!$this->inventory->isAvailable($item))

    { throw new ItemUnavailable(); } if (!$this->catalog->isUserAllowed($this->cart, $item)) { throw new ItemNotAllowed(); } $this->cart->add($item); if ($this->promo->isPromo($item)) { $this->promo->applyItemPromo($item); } $this->cart->save(); $this->inventory->reduceEphemeral($item); } }
  15. 24.

    class Cart { public function updateItem(Item $item, $line) { if

    (!$this->inventory->isAvailable($item)) { throw new ItemUnavailable(); } if (!$this->catalog->isUserAllowed($this->cart, $item)) { throw new ItemNotAllowed(); } $this->cart->update($item, $line); if ($this->promo->isPromo($item)) { $this->promo->applyItemPromo($item); } $this->cart->save(); $this->inventory->reduceEphemeral($item); } }
  16. 25.

    class Cart { public function updateItem(Item $item, $line) { if

    (!$this->inventory->isAvailable($item)) { throw new ItemUnavailable(); } if (!$this->catalog->isUserAllowed($this->cart, $item)) { throw new ItemNotAllowed(); } $this->cart->update($item, $line); if ($this->promo->isPromo($item)) { $this->promo->applyItemPromo($item); } $this->cart->save(); $this->inventory->reduceEphemeral($item); } } 11 of 13 lines (85%) are duplicated from addItem!
  17. 26.

    Unit tests become unmaintainable and complex. class CartTest extends PHPUnit_Framework_TestCase

    { public function testCanAddItem() { // Mock promo // Mock inventory // Mock catalog } public function testUnableToAddItemFromCatalogUserLimit() { // More mocks } public function testAddItemWithPromotionRelated() { // Ugh... } public function testUnableToAddItemFromUnavailability() { // ... } }
  18. 27.
  19. 30.
  20. 31.

    class Subject implements \SplSubject { private $observers; public function __construct()

    { $this->observers = new \SplObjectStorage(); } public function attach(\SplObserver $observer) { $this->observers->attach($observer); } public function detach(\SplObserver $observer) { $this->observers->detach($observer); } public function notify() { foreach ($this->observers as $observer) { $observer->update($this)); } } }
  21. 32.
  22. 34.

    $observer = new Observer(); $subject = new Subject(); $subject->attach($observer); $subject->notify();

    // Observer notified! The subject has the responsibility of the Mediator.
  23. 38.

    Synchronous Separation of Concerns • Provides for modularity • Allow

    for unit testing handlers separately • Allows for priority and interrupts
  24. 39.

    class Cart { public function addItem(Item $item) { if (!$this->inventory->isAvailable($item))

    { throw new ItemUnavailable(); } if (!$this->catalog->isUserAllowed($this->cart, $item)) { throw new ItemNotAllowed(); } $this->cart->add($item); if ($this->promo->isApplicable($this->cart, $item)) { $this->promo->applyItemPromo($item); } $this->inventory->reduceEphemeral($item); $this->cart->save(); } }
  25. 40.

    • Always be descriptive with event names. • Always name

    them in past tense. • Don’t emit the same event from more than one location. Event Conventions
  26. 41.

    class Cart { public function addItem(Item $item) { $this->dispatcher->dispatch( 'cart.addItemStarted',

    new GenericEvent($this->cart, compact('item')); ); $this->cart->add($item); if ($this->promo->isApplicable($this->cart, $item)) { $this->promo->applyItemPromo($item); } $this->inventory->reduceEphemeral($item); $this->cart->save(); } }
  27. 42.

    class Cart { public function addItem(Item $item) { $this->dispatcher->dispatch( 'cart.addItemStarted',

    new GenericEvent($this->cart, compact('item')); ); $this->cart->add($item); $this->dispatcher->dispatch( 'cart.itemAdded', new GenericEvent($this->cart, compact('item')); ); $this->cart->save(); } }
  28. 43.

    class Cart { private function dispatch($name, array $payload = [])

    { return $this->dispatcher->dispatch( 'cart.' . $name, new GenericEvent($this->cart, $payload); ); } public function addItem(Item $item) { $this->dispatch('addItemStarted', compact('item')); $this->cart->add($item); $this->dispatch('itemAdded', compact('item')); $this->cart->save(); } } Almost back to the original simple addItem.
  29. 48.

    class CartTest extends PHPUnit_Framework_TestCase { public function testCanAddItem() { //

    Mock promo // Mock inventory // Mock catalog } public function testUnableToAddItemFromCatalogUserLimit() { // More mocks } public function testAddItemWithPromotionRelated() { // Ugh... } public function testUnableToAddItemFromUnavailability() { // ... } }
  30. 51.

    class InventoryHandler implements EventSubscriberInterface { // construct the inventory manager...

    public static function getSubscribedEvents() { return [ ‘cart.addItemStarted’ => ‘checkAvailability’ ]; } }
  31. 52.

    class InventoryHandler implements EventSubscriberInterface { // construct the inventory manager...

    public static function getSubscribedEvents() { return [ ‘cart.addItemStarted’ => ‘checkAvailability’ ]; } public function checkAvailability(Event $e) { $item = $e->getArgument(‘item’); if (!$this->inventory->isAvailable($item)) { throw new ItemUnavailable(); } } }
  32. 53.

    Asynchronous • Synchronous in order of execution. • Asynchronous in

    order of completion. $('p') .on('click', function() { setTimeout(function() { console.log('first called, second to finish!'); }, 1000); }) .on('click', function() { console.log('second called, first to finish.'); });
  33. 54.

    Synchronous • Predictable order of execution and completion. $event ->on('event1',

    function() { echo 'Always first.'; sleep(1); }) ->on('event1', function() { echo 'Always second.'; });
  34. 56.

    • Symfony 2 • Laravel • CakePHP • Lithium •

    Yii • Zend Framework • Doctrine • Guzzle • Magento Events in the Wild
  35. 57.

    Advantages & Drawbacks • Modular design • Separation of Concerns

    • Side effects are not obvious from looking at code. • Source of event is not always obvious when debugging. Decoupling Code
  36. 58.

    Advantages & Drawbacks • Allows third parties to hook in

    and modify behavior without modifying the core. • Easy to add/remove hooks (even at runtime). • No dependencies should exist between plugin hooks. Plugin Architecture
  37. 59.

    Advantages & Drawbacks • Test handlers separate from the core

    functionality. • Allows for execution of events from manual triggers. • Testing interaction of handlers is cumbersome (functional testing). • Unbinding specific event callbacks can be difficult. Testing
  38. 60.

    Conclusions • We can take a relatively complex system and

    simplify key systems. • We can remove concerns from key systems. • We can modularize concerns into a containable / testable unit. • We can allow for plugin architectures.
  39. 61.

    Further Reading • Use application events to hook in plugins

    • Decoupling applications with domain events • Domain Events (Martin Fowler)
  40. 63.