Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Wrangle Cross-cutting Concerns with Event Driven Development

Chris Saylor
October 05, 2013

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.

Chris Saylor

October 05, 2013
Tweet

More Decks by Chris Saylor

Other Decks in Programming

Transcript

  1. Event Driven
    Applications
    Wrangle Cross-Cutting Concerns

    View Slide

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

    View Slide

  3. A little about Zumba
    ● 180 Countries
    ● 15 million class participants
    ● 60 million service requests per month

    View Slide

  4. Indicates my opinion on a subject.
    Feel free to discuss over beers.

    View Slide

  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.

    View Slide

  6. Cross-cutting examples
    ● Logging
    ● Caching
    ● Product feature interaction
    ● Monitoring
    ● Record audit trail
    ● State Management

    View Slide

  7. class Cart
    {
    public function addItem(Item $item)
    {
    $this->cart->add($item);
    $this->cart->save();
    }
    }

    View Slide

  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();
    }
    }

    View Slide

  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();
    }
    }

    View Slide

  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();
    }
    }

    View Slide

  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();
    }
    }

    View Slide

  12. Our cart is overloaded!

    View Slide

  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

    View Slide

  14. class Cart
    {
    public function addItem(Item $item)
    {
    $this->cart->add($item);
    $this->cart->save();
    }
    }

    View Slide

  15. View Slide

  16. 3 - 4 + 2 * 1 = 1

    View Slide

  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();
    }
    }

    View Slide

  18. View Slide

  19. 13 - 11 + (2 * 1) = 4
    Four times as much complexity!

    View Slide

  20. Risks of cross-cutting concerns
    ● Coupling systems too tightly.
    ● Lots of code duplication.
    ● Brittle Test Cases.

    View Slide

  21. Cart
    Promo Inventory Catalog

    View Slide

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

    View Slide

  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);
    }
    }

    View Slide

  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);
    }
    }

    View Slide

  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!

    View Slide

  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()
    {
    // ...
    }
    }

    View Slide

  27. View Slide

  28. Event Patterns
    Observer Pattern Mediator Pattern

    View Slide

  29. Subject
    Observer
    Observer
    Observer
    Observer
    Observer Pattern

    View Slide

  30. View Slide

  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));
    }
    }
    }

    View Slide

  32. View Slide

  33. class Observer implements \SplObserver
    {
    public function update(\SplSubject $subject)
    {
    echo 'Observer notified!';
    }
    }

    View Slide

  34. $observer = new Observer();
    $subject = new Subject();
    $subject->attach($observer);
    $subject->notify();
    // Observer notified!
    The subject has the responsibility of the Mediator.

    View Slide

  35. Mediator Pattern
    Subject Mediator
    Observer
    Observer
    Subscribe
    Subscribe

    View Slide

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

    View Slide

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

    View Slide

  38. Synchronous
    Separation of Concerns
    ● Provides for modularity
    ● Allow for unit testing handlers separately
    ● Allows for priority and interrupts

    View Slide

  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();
    }
    }

    View Slide

  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

    View Slide

  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();
    }
    }

    View Slide

  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();
    }
    }

    View Slide

  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.

    View Slide

  44. 5 - 6 + (2 * 1) = 1

    View Slide

  45. Cart
    Promo Inventory Catalog

    View Slide

  46. Cart
    Promo Inventory Catalog
    Inversion of responsibility!

    View Slide

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

    View Slide

  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()
    {
    // ...
    }
    }

    View Slide

  49. class CartTest extends PHPUnit_Framework_TestCase
    {
    public function testAddItem() {
    // Mock dispatcher and spy on dispatch
    }
    }

    View Slide

  50. class InventoryHandler implements EventSubscriberInterface
    {
    }

    View Slide

  51. class InventoryHandler implements EventSubscriberInterface
    {
    // construct the inventory manager...
    public static function getSubscribedEvents()
    {
    return [
    ‘cart.addItemStarted’ => ‘checkAvailability’
    ];
    }
    }

    View Slide

  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();
    }
    }
    }

    View Slide

  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.');
    });

    View Slide

  54. Synchronous
    ● Predictable order of execution and
    completion.
    $event
    ->on('event1', function() {
    echo 'Always first.';
    sleep(1);
    })
    ->on('event1', function() {
    echo 'Always second.';
    });

    View Slide

  55. Events in the Wild

    View Slide

  56. ● Symfony 2
    ● Laravel
    ● CakePHP
    ● Lithium
    ● Yii
    ● Zend Framework
    ● Doctrine
    ● Guzzle
    ● Magento
    Events in the Wild

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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.

    View Slide

  61. Further Reading
    ● Use application events to hook in plugins
    ● Decoupling applications with domain events
    ● Domain Events (Martin Fowler)

    View Slide

  62. Questions?

    View Slide

  63. Thanks!

    View Slide

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

    View Slide