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

Better Living Through Events

Better Living Through Events

Effectively decoupling code can be difficult: the user clicks a button and 12 things need to happen, but you don't want those things all tied to your button code. Events to the rescue! In this talk, Bill will discuss events, what they are, patterns for using them, some libraries out there to help you write event-driven code.

Bill Israel

July 09, 2013
Tweet

More Decks by Bill Israel

Other Decks in Technology

Transcript

  1. a hot chicken ordering system Input order Order Heard Order

    Started Order Checked Order Finished Order Up Order delivered
  2. sounds simple enough, right? function updateOrder() { $status = $this->getStatus();

    switch($status) { case 'NEW': $this->setHeardTime(new \DateTime()); $this->setStatus('HEARD')->save(); break; // ... repeat for other states } }
  3. email order updates function updateOrder() { $status = $this->getStatus(); switch($status)

    { case 'NEW': $this->setHeardTime(new \DateTime()); $this->setStatus('HEARD')->save(); break; // ... repeat for other states } $mailer = new Mailer( $this->getCustomer()->getEmail(), '[email protected]', "Your order is now ${this->getStatus()}!" ); $mailer->send(); }
  4. and they want metrics function updateOrder() { $status = $this->getStatus();

    switch($status) { case 'NEW': $this->setHeardTime(new \DateTime()); $this->setStatus('HEARD')->save(); $db = $this->getReportingDB(); $db->timeToHeard( $this->getHeardTime() - $this->getCreatedTime() ); break; // ... repeat for other states } $mailer = new Mailer( $this->getCustomer()->getEmail(), '[email protected]', "Your order is now ${this->getStatus()}!" ); $mailer->send(); }
  5. even more ideas appear Users should receive text messages as

    the state of their order changes Update a progress bar (Hi Josh!) on an UI somewhere on the line Every 10th order, the customer should be sent a satisfaction survey Every 50th order, send the customer a coupon for <something> And think of all the metrics: Count orders that fail quality check Count orders that use coupons and how much they paid Send email for orders taking longer than 15 minutes to fill etc etc etc
  6. AND before long YOU HAVE... function updateOrder() { $status =

    $this->getStatus(); switch($status) { case 'NEW': $this->setHeardTime(new \DateTime()); $this->setStatus('ACCEPTED')->save(); $report = $this->getReportingDB(); $db->timeToAccepted( $this->getAcceptedTime() - $this->getCreatedTime() ); break; // ... repeat for other states case 'HEARD': $this->setCheckedTime(new \DateTime()); $this->setStatus('STARTED')->save(); $report = $this->getReportingDB(); $db->timeToStarted( $this->getStartedTime() - $this->getHeardTime() ); break; case 'STARTED': $this->setCompletedTime(new \DateTime()); $this->setStatus('COMPLETED')->save(); $report = $this->getReportingDB(); $db->timeToChecked( $this->getCheckedTime() - $this->getStartedTime() ); break; case 'COMPLETED': $this->setUpTime(new \DateTime()); $this->setStatus('UP')->save(); $report = $this->getReportingDB(); $db->timeTothisUp( $this->getUpTime() - $this->getCompletedTime() ); break; case 'UP': $this->setDeliveredTime(new \DateTime()); $this->setStatus('DELIVERED')->save(); $report = $this->getReportingDB(); $db->timeToDelivered( $this->getDeliveredTime() - $this->getUpTime() ); break; } $mailer = new Mailer( $this->getCustomer()->getEmail(), '[email protected]', "Your order is now ${this->getStatus()}!" ); $mailer->send(); } lolwut
  7. what do we have? We have an Order object... ...that

    needs to update as the order progresses... ...and when it updates, lots of things need to happen. we need EVENTS
  8. so what is an event? An event is an action

    detected by the system, whose occurrence is announced (the event is “fired”), and anything listening for the announcement performs some action (or actions) in response to the announcement (the event is “handled”). Huh?
  9. observer pattern Two parts: Subject & Observer Simple interface: Observers

    “subscribe” to Subjects Subjects “notify” Observers of changes Observers can “unsubscribe” from Subjects if they no longer want updates subject observer subject observer attach() subject observer update() subject observer detach()
  10. mediator pattern Three parts: Subject, Observer, Mediator How it differs

    from Observer: Observers and Subjects communicate through the Mediator Has a similar interface: Observers “subscribe” to events thrown by the Mediator Subjects tell the Mediator to “publish” an event The Mediator “notifies” the subscribing Observers of the event subject observer mediator attach() update() publish()
  11. what do we have now? We have an Order object...

    ...that needs to update as the order progresses... ...and when it updates, things need to happen... ...and now we need to change it to use events.
  12. splsubject and splobserver interface SplSubject { public attach(\SplObserver $observer) public

    detach(\SplObserver $observer) public notify() } interface SplObserver { public update(\SplSubject $subject) }
  13. order: now AS A subject class Order implements \SplSubject {

    private $observers = new \SplObjectStorage(); function attach(\SplObserver $observer) { $this->observers->attach($observer); } function detach(\SplObserver $observer) { $this->observers->detach($observer); } function notify() { foreach($this->observers as $o) { $o->update($this); } } // ...snip }
  14. EMAIL: NOW AS AN OBSERVER // EmailNotifier.php class EmailNotifier implements

    \SplObserver { public function update(\SplSubject $subject) { $mailer = new Mailer( $subject->getCustomer()->getEmail(), '[email protected]', "Your order is now ${subject->getStatus()}" ); $mailer->send(); } }
  15. reporting: NOW AS AN OBSERVER // OrderReporter.php class OrderReporter implements

    \SplObserver { public function update(\SplSubject $subject) { $status = $subject->getStatus(); $db = $this->getReportingDB(); switch($status) { case 'NEW': $db->timeToHeard( $subject->getHeardTime() - $subject->getCreatedTime() ); break; // ... repeat for other states }
  16. attach those observers // bootstrap.php $order = new Order(); $mail_observer

    = new EmailNotifier(...); $report_observer = new OrderReporter(...); $order->attach($mail_observer); $order->attach($report_observer);
  17. updateorder(): before function updateOrder() { $status = $this->getStatus(); switch($status) {

    case 'NEW': $this->setHeardTime(new \DateTime()); $this->setStatus('HEARD')->save(); $report = $this->getReportingDB(); $db->timeToAccepted( $this->getHeardTime() - $this->getCreatedTime() ); break; // ... repeat for other states } $mailer = new Mailer( $this->getCustomer()->getEmail(), '[email protected]', "Your order is now ${this->getStatus()}!" ); $mailer->send(); }
  18. updateorder(): after function updateOrder() { $status = $this->getStatus(); switch($status) {

    case 'NEW': $this->setHeardTime(new \DateTime()); $this->setStatus('HEARD')->save(); break; // ... repeat for other states } $this->notify($this); }
  19. side by side function updateOrder() { $status = $this->getStatus(); switch($status)

    { case 'NEW': $this->setHeardTime(new \DateTime()); $this->setStatus('ACCEPTED')->save(); $report = $this->getReportingDB(); $db->timeToAccepted( $this->getAcceptedTime() - $this->getCreatedTime() ); break; // ... repeat for other states case 'HEARD': $this->setCheckedTime(new \DateTime()); $this->setStatus('STARTED')->save(); $report = $this->getReportingDB(); $db->timeToStarted( $this->getStartedTime() - $this->getHeardTime() ); break; case 'STARTED': $this->setCompletedTime(new \DateTime()); $this->setStatus('COMPLETED')->save(); $report = $this->getReportingDB(); $db->timeToChecked( $this->getCheckedTime() - $this->getStartedTime() ); break; case 'COMPLETED': $this->setUpTime(new \DateTime()); $this->setStatus('UP')->save(); $report = $this->getReportingDB(); $db->timeTothisUp( $this->getUpTime() - $this->getCompletedTime() ); break; case 'UP': $this->setDeliveredTime(new \DateTime()); $this->setStatus('DELIVERED')->save(); $report = $this->getReportingDB(); $db->timeToDelivered( $this->getDeliveredTime() - $this->getUpTime() ); break; } $mailer = new Mailer( $this->getCustomer()->getEmail(), '[email protected]', "Your order is now ${this->getStatus()}!" ); $mailer->send(); } function updateOrder() { $status = $this->getStatus(); switch($status) { case 'NEW': $this->setHeardTime(new \DateTime()); $this->setStatus('ACCEPTED')->save(); break; case 'HEARD': $this->setCheckedTime(new \DateTime()); $this->setStatus('STARTED')->save(); break; case 'STARTED': $this->setCompletedTime(new \DateTime()); $this->setStatus('COMPLETED')->save(); break; case 'COMPLETED': $this->setUpTime(new \DateTime()); $this->setStatus('UP')->save(); break; case 'UP': $this->setDeliveredTime(new \DateTime()); $this->setStatus('DELIVERED')->save(); break; } $this->notify($this); }
  20. so what do we have now? Email notifications and the

    reporting DB updates are no longer coupled to the Order object Other objects now have a mechanism for telling an Order that they want to know when it updates Order now has a mechanism for telling other objects when it updates And neither need to know that the other exists Using events here... Made our order update code cleaner, more focused, better encapsulated Decoupled our objects from each other
  21. “customer loyalty program” // FreeChicken.php class FreeChicken implements \SplObserver {

    public function update(\SplSubject $subject) { if ($subject->getStatus !== 'COMPLETED') { return; } $customer = $subject->getCustomer(); $orders = count($customer->getOrders()); if ($orders > 0 && $orders % 50 === 0)) { $customer->sendCouponForFreeChicken(); } } }
  22. “customer loyalty program”, cont’d // bootstrap.php $order = new Order();

    $mail_observer = new EmailNotifier(...); $report_observer = new OrderReporter(...); $free_chicken = new FreeChicken(...); $order->attach($mail_observer); $order->attach($report_observer); $order->attach($free_chicken);
  23. let’s try again... Email notifications and the reporting DB updates

    are no longer coupled to the Order object Other objects now have a mechanism for telling an Order that they want to know when it updates Order now has a mechanism for telling other objects when it updates And neither needs to know that the other exists Using events here... Made our order update code cleaner, more focused, better encapsulated Decoupled our objects from each other Made changes/additions easier
  24. no more subject // Mediator.php class Mediator { private $events

    = array(); public function attach($event, $observer) { if (isset($this->events[$event])) { $this->events[$event]->attach($observer); } else { $this->events[$event] = new \SplObjectStorage(); $this->events[$event]->attach($observer); } } // ... wait for it
  25. no more subject, CONT’d // ... wait for it public

    function detach($event, $observer) { if (isset($this->events[$event])) { $this->events[$event]->detach($observer); } } public function publish($event, $subject) { if (isset($this->events[$event])) { foreach($this->events[$event] as $ob) { $ob->update($subject); } } } }
  26. see, there’s still a subject class Order { function updateOrder($order)

    { $status = $order->getStatus(); switch($status) { case 'NEW': $order->setAcceptedTime(new \DateTime()); $order->setStatus('ACCEPTED')->save(); break; // ...repeat for other states } $this->get('mediator')->publish('order.update', $this); } }
  27. mediator hook ups // bootstrap.php $dispatcher = new Mediator(); $mail_observer

    = new EmailNotifier( ... ); $report_observer = new OrderReporter( ... ); $free_chicken = new FreeChicken( ... ); $dispatcher->attach('order.update', $mail_observer); $dispatcher->attach('order.update', $report_observer); $dispatcher->attach('order.update', $free_chicken);
  28. once more, with feeling Events help decouple and clean up

    code when you have many (or an unknown number) of objects that need to know when the state of an object changes. Think: UI code, state machines, saving objects, beginning/ending a process... Observer Pattern Simpler, but more limited Better for small numbers of objects, persistent object, or singletons SplSubject/SplObserver Mediator Pattern (Slightly) More Complicated, but more flexible Typically better for web applications Symfony’s EventDispatcher component
  29. more with events Task/Message queues Event Loops IRC Bots Web

    servers NodeJS (see also: ReactPHP) Event-Driven Architecture