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. Event Driven Applications Wrangle Cross-Cutting Concerns

  2. Chris Saylor Lead Engineer @zumbatech www.chris-saylor.com @cjsaylor

  3. A little about Zumba • 180 Countries • 15 million

    class participants • 60 million service requests per month
  4. Indicates my opinion on a subject. Feel free to discuss

    over beers.
  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.
  6. Cross-cutting examples • Logging • Caching • Product feature interaction

    • Monitoring • Record audit trail • State Management
  7. class Cart { public function addItem(Item $item) { $this->cart->add($item); $this->cart->save();

    } }
  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(); } }
  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(); } }
  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(); } }
  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(); } }
  12. Our cart is overloaded!

  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
  14. class Cart { public function addItem(Item $item) { $this->cart->add($item); $this->cart->save();

    } }
  15. None
  16. 3 - 4 + 2 * 1 = 1

  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(); } }
  18. None
  19. 13 - 11 + (2 * 1) = 4 Four

    times as much complexity!
  20. Risks of cross-cutting concerns • Coupling systems too tightly. •

    Lots of code duplication. • Brittle Test Cases.
  21. Cart Promo Inventory Catalog

  22. Cart Promo Inventory Catalog Changing Promo can affect Cart.

  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); } }
  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); } }
  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!
  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() { // ... } }
  27. None
  28. Event Patterns Observer Pattern Mediator Pattern

  29. Subject Observer Observer Observer Observer Observer Pattern

  30. None
  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)); } } }
  32. None
  33. class Observer implements \SplObserver { public function update(\SplSubject $subject) {

    echo 'Observer notified!'; } }
  34. $observer = new Observer(); $subject = new Subject(); $subject->attach($observer); $subject->notify();

    // Observer notified! The subject has the responsibility of the Mediator.
  35. Mediator Pattern Subject Mediator Observer Observer Subscribe Subscribe

  36. Separation of Concerns Cart Promo Handler Inventory Handler Catalog Handler

  37. Separation of Concerns Cart Promo Handler Inventory Handler Catalog Handler

    Event Bus
  38. Synchronous Separation of Concerns • Provides for modularity • Allow

    for unit testing handlers separately • Allows for priority and interrupts
  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(); } }
  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
  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(); } }
  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(); } }
  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.
  44. 5 - 6 + (2 * 1) = 1

  45. Cart Promo Inventory Catalog

  46. Cart Promo Inventory Catalog Inversion of responsibility!

  47. Cart Promo Inventory Catalog Riskiest piece now changes less frequently.

  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() { // ... } }
  49. class CartTest extends PHPUnit_Framework_TestCase { public function testAddItem() { //

    Mock dispatcher and spy on dispatch } }
  50. class InventoryHandler implements EventSubscriberInterface { }

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

    public static function getSubscribedEvents() { return [ ‘cart.addItemStarted’ => ‘checkAvailability’ ]; } }
  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(); } } }
  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.'); });
  54. Synchronous • Predictable order of execution and completion. $event ->on('event1',

    function() { echo 'Always first.'; sleep(1); }) ->on('event1', function() { echo 'Always second.'; });
  55. Events in the Wild

  56. • Symfony 2 • Laravel • CakePHP • Lithium •

    Yii • Zend Framework • Doctrine • Guzzle • Magento Events in the Wild
  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
  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
  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
  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.
  61. Further Reading • Use application events to hook in plugins

    • Decoupling applications with domain events • Domain Events (Martin Fowler)
  62. Questions?

  63. Thanks!

  64. Please leave feedback! https://joind.in/14244