Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Twitter: @matthiasnoback
Matthias Noback
A Series of Fortunate Events
What are events, really?
Things that happen
They trigger actions
Just now...
Attendees arrived,
triggered me to turn on microphone,
which triggered you to stop talking,
which triggered me...
Events in software
Events model what happened in a system
Other parts of the system
can respond to what happened
Imperative programming
Only commands
doThis();
doThat();
updateSomething($something);
return $something;
Extracting events
doThis();
// this was done
doThat();
// that was done
updateSomething($something)
// something was updat...
Starting position
class PostService
{
...
function addComment($postId, $comment)
{
$post = $this->fetchPost($postId);
$pos...
Starting position
class PostService
{
function __construct(
Mailer $mailer,
Logger $logger
) {
$this->mailer = $mailer;
$t...
Making events explicit
class PostService
{
function addComment($postId, $comment)
{
...
$this->newCommentAdded();
}
functi...
Dependency graph
PostServicePostService
Mailer
Logger
Design issues (1)
I don't think the PostService should
know how to use a Mailer and a Logger
Design issues (2)
I want to change the behavior of
PostService without modifying the
class itself
Fix the problems
By introducing events!
(later)
Observer pattern
Notify other parts of the application
when a change occurs
class PostService
{
function newCommentAdded()...
Observer contract
interface Observer
{
function notify();
}
Subject knows nothing about its
observers, except their very s...
Concrete observers
class LoggingObserver implements Observer
{
function __construct(Logger $logger)
{
$this->logger = $log...
Concrete observers
class NotificationMailObserver implements Observer
{
function __construct(Mailer $mailer)
{
$this->mail...
Configuration
class PostService
{
function __construct(array $observers)
{
$this->observers = $observers;
}
}
$postService...
Before
PostServicePostService
Mailer
Logger
After
NotificationMailObserver
Observer
Observer
LoggingObserver
Mailer
Logger
PostService
Design Principles Party
Single responsibility
Each class has one small,
well-defined responsibility
Single responsibility
● PostService:
“add comments to posts”
● LoggingObserver:
“write a line to the log”
● NotificationMa...
Single responsibility
When a change is required, it can be
isolated to just a small part of the
application
Single responsibility
●
“Capitalize the comment!”:
PostService
●
“Use a different logger!”:
LoggerObserver
●
“Add a timest...
Dependency inversion
Depend on abstractions,noton
concretions
Dependency inversion
First PostService depended on
something concrete: the Mailer,the
Logger.
Mailer
LoggerPostService
Dependency inversion
Now it depends on something abstract:
an Observer
Observer
Observer
PostService
Dependency inversion
Only the concrete observers depend on
concrete things like Mailer and Logger
NotificationMailObserver
LoggingObserver
Mailer
Logger
Open/closed
A class should be open for extension and
closed for modification
Open/closed
You don't need to modify the class to
change its behavior
Observer
Observer Observer
PostService
Open/closed
We made it closed for modification,
open for extension
Event data
Mr. Boddy was murdered!
● By Mrs. Peacock
● In the dining room
● With a candle stick
Currently missing!
class LogNewCommentObserver implements Observer
{
function notify()
{
// we'd like to be more specific
...
Event object
class CommentAddedEvent
{
public function __construct($postId, $comment)
{
$this->postId = $postId;
$this->co...
Event object
We use the event object
to store the context of the event
From observer...
interface Observer
{
function notify();
}
… to event handler
interface CommentAddedEventHandler
{
function handle(CommentAddedEvent $event);
}
Event handlers
class LoggingEventHandler implements
CommentAddedEventHandler
{
function __construct(Logger $logger)
{
$thi...
Event handlers
class NotificationMailEventHandler implements
CommentAddedEventHandler
{
function __construct(Mailer $maile...
Configuration
class PostService
{
function __construct(array $eventHandlers)
{
$this->eventHandlers = $eventHandlers;
}
}
...
Looping over event handlers
class PostService
{
public function addComment($postId, $comment)
{
$this->newCommentAdded($po...
Introducing a Mediator
Instead of talking to the event handlers
Let's leave the talking to a mediator
Mediators for events
●
Doctrine, Zend: Event manager
●
The PHP League: Event emitter
●
Symfony: Event dispatcher
Before
LoggingEventHandler::handle()
NotificationMailEventHandler::handle()
PostService
After
LoggingEventHandler::handle()
NotificationMailEventHandler::handle()
EventDispatcherPostService
In code
class PostService
{
function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
...
Event class
use SymfonyComponentEventDispatcherEvent;
class CommentAddedEvent extends Event
{
...
}
Custom event classes s...
Configuration
use SymfonyComponentEventDispatcherEvent;
$dispatcher = new EventDispatcher();
$loggingEventHandler = new
Lo...
Symfony2
●
An event dispatcher is available as the
event_dispatcher service
●
You can register event listeners using
servi...
Inject the event dispatcher
# services.yml
services:
post_service:
class: PostService
arguments: [@event_dispatcher]
Register your listeners
# services.yml
services:
...
logging_event_handler:
class: LoggingEventHandler
arguments: [@logger...
Events and application flow
Symfony2 uses events to generate
response for any given HTTP request
The HttpKernel
$request = Request::createFromGlobals();
// $kernel is in an instance of HttpKernelInterface
$response = $k...
Kernel events
kernel.request
●
Route matching
●
Authentication
kernel.controller
●
Replace the controller
●
Do some access checks
kernel.response
●
Modify the response
●
E.g. inject the Symfony toolbar
Special types of events
●
Kernel events are not merely
notifications
●
They allow other parts of the
application to step i...
Chain of responsibility
Handler 3Handler 1 Handler 2
Some sort
of request
Some sort
of request
Response
Some sort
of reque...
Symfony example
Listener 3Listener 1 Listener 2
Exception! Exception!
Response
I've got an exception!
What should I tell t...
Propagation
class HandleExceptionListener
{
function onKernelException(
GetResponseForExceptionEvent $event
) {
$event->se...
Priorities
$dispatcher = new EventDispatcher();
$dispatcher->addListener(
'comment_added',
array($object, $method),
// pri...
Concerns
Concern 1: Hard to understand
“Click-through understanding” impossible
$event = new CommentAddedEvent($postId, $comment);
...
interface EventDispatcherInterface
{
function dispatch($eventName, Event $event = null);
...
}
Solution
Use Xdebug
Concern 2: Out-of-domain concepts
●
“Comment”
●
“PostId”
●
“Add comment to post”
●
“Dispatcher” (?!)
We did a good thing
We fixed coupling issues
She's called Cohesion
But this guy, Coupling,
has a sister
Cohesion
● Belonging together
● Concepts like “dispatcher”, “event listener”, even
“event”, don't belong in your code
Solutions (1)
Descriptive, explicit naming:
● NotificationMailEventListenerbecomes
SendNotificationMailWhenCommentAdded
● ...
Solutions (1)
This also hides implementation details!
Solutions (2)
Use an event dispatcher for things
that are not naturally cohesive anyway
Solutions (2)
Use something else
when an event dispatcher causes low cohesion
Example: resolving the controller
$event = new GetResponseEvent($request);
$dispatcher->dispatch('kernel.request', $event)...
Concern 3: Loss of control
●
You rely on event listeners to do some really
important work
●
How do you know if they are in...
Solution
●
“Won't fix”
●
You have to learn to live with it
It's good
Inversion of control
exercise
control
give up
control!
Just like...
●
A router determines the right controller
●
The service container injects the right
constructor arguments
●
...
I admit, inversion of control can be scary
But it will
●
lead to better design
●
require less change
●
make maintenance easier
PresentationFinished
AskQuestionsWhenPresentationFinished
SayThankYouWhenNoMoreQuestions
Class and package design principles
http://leanpub.com/principles-of-package-design/c/phpbnl15
Get it for $ 19
Design patterns
●
Observer
●
Mediator
●
Chain of responsibility
●
...
Design Patterns by “The Gang of Four”
SOLID principles
●
Single responsibility
●
Open/closed
●
Dependency inversion
●
...
Agile Software Development by Robert C...
Images
●
www.ohiseered.com/2011_11_01_archive.html
●
Mrs. Peacock, Candlestick:
www.cluecult.com
●
Leonardo DiCaprio:
scre...
Twitter: @matthiasnoback
joind.in/13120
What did you think?
Upcoming SlideShare
Loading in …5
×

A Series of Fortunate Events - PHP Benelux Conference 2015

1,480 views

Published on

What is an event really? How can you best describe an event in your code? What types of events are there, and how do you decide whether or not to implement something as an event?

In this talk we start with a straightforward command-only piece of code. We extract events from it and start moving the event handling code out, trying different design patterns on the way. First we try Observer. Then we introduce event data, event handlers and a Mediator between our code and the event handlers. Finally we pick a well-known event dispatcher implementation (the Symfony EventDispatcher) and see how it uses the Chain of Responsibility design pattern to control the entire flow of a web application request.

In the end I will answer some burning questions like: is it safe to use events all over the place and rely on event handlers to do some really important stuff? How do I overcome the indirection in my event-driven code? And how can I quickly find out what happens where?

Published in: Technology

A Series of Fortunate Events - PHP Benelux Conference 2015

  1. 1. Twitter: @matthiasnoback Matthias Noback A Series of Fortunate Events
  2. 2. What are events, really? Things that happen
  3. 3. They trigger actions
  4. 4. Just now... Attendees arrived, triggered me to turn on microphone, which triggered you to stop talking, which triggered me to start talking
  5. 5. Events in software Events model what happened in a system
  6. 6. Other parts of the system can respond to what happened
  7. 7. Imperative programming Only commands doThis(); doThat(); updateSomething($something); return $something;
  8. 8. Extracting events doThis(); // this was done doThat(); // that was done updateSomething($something) // something was updated return $something;
  9. 9. Starting position class PostService { ... function addComment($postId, $comment) { $post = $this->fetchPost($postId); $post->addComment($comment); $this->save($post); $this->logger->info('New comment'); $this->mailer->send('New comment'); } }
  10. 10. Starting position class PostService { function __construct( Mailer $mailer, Logger $logger ) { $this->mailer = $mailer; $this->logger = $logger; } function addComment($postId, $comment) { ... } }
  11. 11. Making events explicit class PostService { function addComment($postId, $comment) { ... $this->newCommentAdded(); } function newCommentAdded() { $this->logger->info('New comment'); $this->mailer->send('New comment'); } }
  12. 12. Dependency graph PostServicePostService Mailer Logger
  13. 13. Design issues (1) I don't think the PostService should know how to use a Mailer and a Logger
  14. 14. Design issues (2) I want to change the behavior of PostService without modifying the class itself
  15. 15. Fix the problems By introducing events! (later)
  16. 16. Observer pattern Notify other parts of the application when a change occurs class PostService { function newCommentAdded() { foreach ($this->observers as $observer) { $observer->notify(); } } }
  17. 17. Observer contract interface Observer { function notify(); } Subject knows nothing about its observers, except their very simple interface
  18. 18. Concrete observers class LoggingObserver implements Observer { function __construct(Logger $logger) { $this->logger = $logger; } function notify() { $this->logger->info('New comment'); } }
  19. 19. Concrete observers class NotificationMailObserver implements Observer { function __construct(Mailer $mailer) { $this->mailer = $mailer; } function notify() { $this->mailer->send('New comment'); } }
  20. 20. Configuration class PostService { function __construct(array $observers) { $this->observers = $observers; } } $postService = new PostService( array( new LoggingObserver($logger), new NotificationMailObserver($mailer) ) );
  21. 21. Before PostServicePostService Mailer Logger
  22. 22. After NotificationMailObserver Observer Observer LoggingObserver Mailer Logger PostService
  23. 23. Design Principles Party
  24. 24. Single responsibility Each class has one small, well-defined responsibility
  25. 25. Single responsibility ● PostService: “add comments to posts” ● LoggingObserver: “write a line to the log” ● NotificationMailObserver: “send a notification mail”
  26. 26. Single responsibility When a change is required, it can be isolated to just a small part of the application
  27. 27. Single responsibility ● “Capitalize the comment!”: PostService ● “Use a different logger!”: LoggerObserver ● “Add a timestamp to the notification mail!”: NotificationMailObserver
  28. 28. Dependency inversion Depend on abstractions,noton concretions
  29. 29. Dependency inversion First PostService depended on something concrete: the Mailer,the Logger.
  30. 30. Mailer LoggerPostService
  31. 31. Dependency inversion Now it depends on something abstract: an Observer
  32. 32. Observer Observer PostService
  33. 33. Dependency inversion Only the concrete observers depend on concrete things like Mailer and Logger
  34. 34. NotificationMailObserver LoggingObserver Mailer Logger
  35. 35. Open/closed A class should be open for extension and closed for modification
  36. 36. Open/closed You don't need to modify the class to change its behavior
  37. 37. Observer Observer Observer PostService
  38. 38. Open/closed We made it closed for modification, open for extension
  39. 39. Event data Mr. Boddy was murdered! ● By Mrs. Peacock ● In the dining room ● With a candle stick
  40. 40. Currently missing! class LogNewCommentObserver implements Observer { function notify() { // we'd like to be more specific $this->logger->info('New comment'); } }
  41. 41. Event object class CommentAddedEvent { public function __construct($postId, $comment) { $this->postId = $postId; $this->comment = $comment; } function comment() { return $this->comment; } function postId() { return $this->postId; } }
  42. 42. Event object We use the event object to store the context of the event
  43. 43. From observer... interface Observer { function notify(); }
  44. 44. … to event handler interface CommentAddedEventHandler { function handle(CommentAddedEvent $event); }
  45. 45. Event handlers class LoggingEventHandler implements CommentAddedEventHandler { function __construct(Logger $logger) { $this->logger = $logger; } public function handle(CommentAddedEvent $event) { $this->logger->info( 'New comment' . $event->comment() ); } }
  46. 46. Event handlers class NotificationMailEventHandler implements CommentAddedEventHandler { function __construct(Mailer $mailer) { $this->mailer = $mailer; } public function handle(CommentAddedEvent $event) { $this->mailer->send( 'New comment: ' . $event->comment(); ); } }
  47. 47. Configuration class PostService { function __construct(array $eventHandlers) { $this->eventHandlers = $eventHandlers; } } $postService = new PostService( array( new LoggingEventHandler($logger), new NotificationMailEventHandler($mailer) ) );
  48. 48. Looping over event handlers class PostService { public function addComment($postId, $comment) { $this->newCommentAdded($postId, $comment); } function newCommentAdded($postId, $comment) { $event = new CommentAddedEvent( $postId, $comment ); foreach ($this->eventHandlers as $eventHandler) { $eventHandler->handle($event); } } }
  49. 49. Introducing a Mediator Instead of talking to the event handlers Let's leave the talking to a mediator
  50. 50. Mediators for events ● Doctrine, Zend: Event manager ● The PHP League: Event emitter ● Symfony: Event dispatcher
  51. 51. Before LoggingEventHandler::handle() NotificationMailEventHandler::handle() PostService
  52. 52. After LoggingEventHandler::handle() NotificationMailEventHandler::handle() EventDispatcherPostService
  53. 53. In code class PostService { function __construct(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; } function newCommentAdded($postId, $comment) { $event = new CommentAddedEvent($postId, $comment); $this->dispatcher->dispatch( 'comment_added', $event ); } }
  54. 54. Event class use SymfonyComponentEventDispatcherEvent; class CommentAddedEvent extends Event { ... } Custom event classes should extend Symfony Event class:
  55. 55. Configuration use SymfonyComponentEventDispatcherEvent; $dispatcher = new EventDispatcher(); $loggingEventHandler = new LoggingEventHandler($logger); $dispatcher->addListener( 'comment_added', array($loggingEventHandler, 'handle') ); ... $postService = new PostService($dispatcher);
  56. 56. Symfony2 ● An event dispatcher is available as the event_dispatcher service ● You can register event listeners using service tags
  57. 57. Inject the event dispatcher # services.yml services: post_service: class: PostService arguments: [@event_dispatcher]
  58. 58. Register your listeners # services.yml services: ... logging_event_handler: class: LoggingEventHandler arguments: [@logger] tags: - { name: kernel.event_listener event: comment_added method: handle }
  59. 59. Events and application flow Symfony2 uses events to generate response for any given HTTP request
  60. 60. The HttpKernel $request = Request::createFromGlobals(); // $kernel is in an instance of HttpKernelInterface $response = $kernel->handle($request); $response->send();
  61. 61. Kernel events
  62. 62. kernel.request ● Route matching ● Authentication
  63. 63. kernel.controller ● Replace the controller ● Do some access checks
  64. 64. kernel.response ● Modify the response ● E.g. inject the Symfony toolbar
  65. 65. Special types of events ● Kernel events are not merely notifications ● They allow other parts of the application to step in and modify or override behavior
  66. 66. Chain of responsibility Handler 3Handler 1 Handler 2 Some sort of request Some sort of request Response Some sort of request
  67. 67. Symfony example Listener 3Listener 1 Listener 2 Exception! Exception! Response I've got an exception! What should I tell the user?
  68. 68. Propagation class HandleExceptionListener { function onKernelException( GetResponseForExceptionEvent $event ) { $event->setResponse(new Response('Error!')); // this is the best response ever, don't let // others spoil it! $event->stopPropagation(); } }
  69. 69. Priorities $dispatcher = new EventDispatcher(); $dispatcher->addListener( 'comment_added', array($object, $method), // priority 100 );
  70. 70. Concerns
  71. 71. Concern 1: Hard to understand “Click-through understanding” impossible $event = new CommentAddedEvent($postId, $comment); $this->dispatcher->dispatch( 'comment_added', $event );
  72. 72. interface EventDispatcherInterface { function dispatch($eventName, Event $event = null); ... }
  73. 73. Solution Use Xdebug
  74. 74. Concern 2: Out-of-domain concepts ● “Comment” ● “PostId” ● “Add comment to post” ● “Dispatcher” (?!)
  75. 75. We did a good thing We fixed coupling issues
  76. 76. She's called Cohesion But this guy, Coupling, has a sister
  77. 77. Cohesion ● Belonging together ● Concepts like “dispatcher”, “event listener”, even “event”, don't belong in your code
  78. 78. Solutions (1) Descriptive, explicit naming: ● NotificationMailEventListenerbecomes SendNotificationMailWhenCommentAdded ● CommentAddedEvent becomes CommentAdded ● onCommentAdded becomes whenCommentAdded
  79. 79. Solutions (1) This also hides implementation details!
  80. 80. Solutions (2) Use an event dispatcher for things that are not naturally cohesive anyway
  81. 81. Solutions (2) Use something else when an event dispatcher causes low cohesion
  82. 82. Example: resolving the controller $event = new GetResponseEvent($request); $dispatcher->dispatch('kernel.request', $event); $controller = $request->attributes->get('_controller'); $controller = $controllerResolver->resolve($request);
  83. 83. Concern 3: Loss of control ● You rely on event listeners to do some really important work ● How do you know if they are in place and do their job?
  84. 84. Solution ● “Won't fix” ● You have to learn to live with it
  85. 85. It's good
  86. 86. Inversion of control exercise control give up control!
  87. 87. Just like... ● A router determines the right controller ● The service container injects the right constructor arguments ● And when you die, someone will bury your body for you
  88. 88. I admit, inversion of control can be scary
  89. 89. But it will ● lead to better design ● require less change ● make maintenance easier
  90. 90. PresentationFinished AskQuestionsWhenPresentationFinished SayThankYouWhenNoMoreQuestions
  91. 91. Class and package design principles http://leanpub.com/principles-of-package-design/c/phpbnl15 Get it for $ 19
  92. 92. Design patterns ● Observer ● Mediator ● Chain of responsibility ● ... Design Patterns by “The Gang of Four”
  93. 93. SOLID principles ● Single responsibility ● Open/closed ● Dependency inversion ● ... Agile Software Development by Robert C. Martin
  94. 94. Images ● www.ohiseered.com/2011_11_01_archive.html ● Mrs. Peacock, Candlestick: www.cluecult.com ● Leonardo DiCaprio: screenrant.com/leonardo-dicaprio-defends-wolf-wall-street-controversy/ ● Book covers: Amazon ● Party: todesignoffsite.com/events-2/to-do-closing-party-with-love-design/ ● Russell Crowe: malinaelena.wordpress.com/2014/04/18/top-8-filme-cu-russell-crowe/
  95. 95. Twitter: @matthiasnoback joind.in/13120 What did you think?

×