95
Twitter: @matthiasnoback Matthias Noback A Series of Fortunate Events

A Series of Fortunate Events - PHP Benelux Conference 2015

Embed Size (px)

Citation preview

Page 1: A Series of Fortunate Events - PHP Benelux Conference 2015

Twitter: @matthiasnoback

Matthias Noback

A Series of Fortunate Events

Page 2: A Series of Fortunate Events - PHP Benelux Conference 2015

What are events, really?

Things that happen

Page 3: A Series of Fortunate Events - PHP Benelux Conference 2015

They trigger actions

Page 4: A Series of Fortunate Events - PHP Benelux Conference 2015

Just now...Attendees arrived,

triggered me to turn on microphone,

which triggered you to stop talking,

which triggered me to start talking

Page 5: A Series of Fortunate Events - PHP Benelux Conference 2015

Events in software Events model what happened in a system

Page 6: A Series of Fortunate Events - PHP Benelux Conference 2015

Other parts of the system can respond to what happened

Page 7: A Series of Fortunate Events - PHP Benelux Conference 2015

Imperative programmingOnly commands

doThis();

doThat();

updateSomething($something);

return $something;

Page 8: A Series of Fortunate Events - PHP Benelux Conference 2015

Extracting eventsdoThis();// this was done

doThat();// that was done

updateSomething($something)// something was updated

return $something;

Page 9: A Series of Fortunate Events - PHP Benelux Conference 2015

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

Page 10: A Series of Fortunate Events - PHP Benelux Conference 2015

Starting positionclass PostService { function __construct( Mailer $mailer, Logger $logger ) { $this->mailer = $mailer; $this->logger = $logger; }

function addComment($postId, $comment) { ... }}

Page 11: A Series of Fortunate Events - PHP Benelux Conference 2015

Making events explicitclass PostService { function addComment($postId, $comment) { ... $this->newCommentAdded(); }

function newCommentAdded() { $this->logger->info('New comment'); $this->mailer->send('New comment'); }}

Page 12: A Series of Fortunate Events - PHP Benelux Conference 2015

Dependency graph

PostServicePostService

Mailer

Logger

Page 13: A Series of Fortunate Events - PHP Benelux Conference 2015

Design issues (1)I don't think the PostService should know how to use a Mailer and a Logger

Page 14: A Series of Fortunate Events - PHP Benelux Conference 2015

Design issues (2)I want to change the behavior of PostService without modifying the class itself

Page 15: A Series of Fortunate Events - PHP Benelux Conference 2015

Fix the problems

By introducing events!(later)

Page 16: A Series of Fortunate Events - PHP Benelux Conference 2015

Observer patternNotify other parts of the application when a change occurs

class PostService { function newCommentAdded() { foreach ($this->observers as $observer) { $observer->notify(); } }}

Page 17: A Series of Fortunate Events - PHP Benelux Conference 2015

Observer contract

interface Observer{ function notify();}

Subject knows nothing about its observers, except their very simple interface

Page 18: A Series of Fortunate Events - PHP Benelux Conference 2015

Concrete observersclass LoggingObserver implements Observer{ function __construct(Logger $logger) { $this->logger = $logger; }

function notify() { $this->logger->info('New comment'); }}

Page 19: A Series of Fortunate Events - PHP Benelux Conference 2015

Concrete observers

class NotificationMailObserver implements Observer{ function __construct(Mailer $mailer) { $this->mailer = $mailer; }

function notify() { $this->mailer->send('New comment'); }}

Page 20: A Series of Fortunate Events - PHP Benelux Conference 2015

Configurationclass PostService{ function __construct(array $observers) { $this->observers = $observers; }}

$postService = new PostService( array( new LoggingObserver($logger), new NotificationMailObserver($mailer) ));

Page 21: A Series of Fortunate Events - PHP Benelux Conference 2015

Before

PostServicePostService

Mailer

Logger

Page 22: A Series of Fortunate Events - PHP Benelux Conference 2015

After

NotificationMailObserver

Observer

Observer

LoggingObserver

Mailer

Logger

PostService

Page 23: A Series of Fortunate Events - PHP Benelux Conference 2015

Design Principles Party

Page 24: A Series of Fortunate Events - PHP Benelux Conference 2015

Single responsibilityEach class has one small,

well-defined responsibility

Page 25: A Series of Fortunate Events - PHP Benelux Conference 2015

Single responsibility● PostService:

“add comments to posts”

● LoggingObserver: “write a line to the log”

● NotificationMailObserver: “send a notification mail”

Page 26: A Series of Fortunate Events - PHP Benelux Conference 2015

Single responsibilityWhen a change is required, it can be isolated to just a small part of the application

Page 27: A Series of Fortunate Events - PHP Benelux Conference 2015

Single responsibility● “Capitalize the comment!”: PostService

● “Use a different logger!”: LoggerObserver

● “Add a timestamp to the notification mail!”: NotificationMailObserver

Page 28: A Series of Fortunate Events - PHP Benelux Conference 2015

Dependency inversionDepend on abstractions, not on concretions

Page 29: A Series of Fortunate Events - PHP Benelux Conference 2015

Dependency inversionFirst PostService depended on something concrete: the Mailer, the Logger.

Page 30: A Series of Fortunate Events - PHP Benelux Conference 2015

Mailer

LoggerPostService

Page 31: A Series of Fortunate Events - PHP Benelux Conference 2015

Dependency inversionNow it depends on something abstract: an Observer

Page 32: A Series of Fortunate Events - PHP Benelux Conference 2015

Observer

Observer

PostService

Page 33: A Series of Fortunate Events - PHP Benelux Conference 2015

Dependency inversionOnly the concrete observers depend on concrete things like Mailer and Logger

Page 34: A Series of Fortunate Events - PHP Benelux Conference 2015

NotificationMailObserver

LoggingObserver

Mailer

Logger

Page 35: A Series of Fortunate Events - PHP Benelux Conference 2015

Open/closedA class should be open for extension and closed for modification

Page 36: A Series of Fortunate Events - PHP Benelux Conference 2015

Open/closedYou don't need to modify the class to change its behavior

Page 37: A Series of Fortunate Events - PHP Benelux Conference 2015

Observer

Observer Observer

PostService

Page 38: A Series of Fortunate Events - PHP Benelux Conference 2015

Open/closedWe made it closed for modification,

open for extension

Page 39: A Series of Fortunate Events - PHP Benelux Conference 2015

Event data

Mr. Boddy was murdered!● By Mrs. Peacock

● In the dining room

● With a candle stick

Page 40: A Series of Fortunate Events - PHP Benelux Conference 2015

Currently missing!

class LogNewCommentObserver implements Observer{ function notify() { // we'd like to be more specific $this->logger->info('New comment'); }}

Page 41: A Series of Fortunate Events - PHP Benelux Conference 2015

Event objectclass CommentAddedEvent { public function __construct($postId, $comment) { $this->postId = $postId; $this->comment = $comment; }

function comment() { return $this->comment; }

function postId() { return $this->postId; }}

Page 42: A Series of Fortunate Events - PHP Benelux Conference 2015

Event object

We use the event object to store the context of the event

Page 43: A Series of Fortunate Events - PHP Benelux Conference 2015

From observer...

interface Observer{ function notify();}

Page 44: A Series of Fortunate Events - PHP Benelux Conference 2015

… to event handler

interface CommentAddedEventHandler{ function handle(CommentAddedEvent $event);}

Page 45: A Series of Fortunate Events - PHP Benelux Conference 2015

Event handlersclass LoggingEventHandler implements CommentAddedEventHandler{ function __construct(Logger $logger) { $this->logger = $logger; }

public function handle(CommentAddedEvent $event) { $this->logger->info( 'New comment' . $event->comment() ); }}

Page 46: A Series of Fortunate Events - PHP Benelux Conference 2015

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

Page 47: A Series of Fortunate Events - PHP Benelux Conference 2015

Configuration

class PostService{ function __construct(array $eventHandlers) { $this->eventHandlers = $eventHandlers; }}

$postService = new PostService( array( new LoggingEventHandler($logger), new NotificationMailEventHandler($mailer) ));

Page 48: A Series of Fortunate Events - PHP Benelux Conference 2015

Looping over event handlersclass 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); } }}

Page 49: A Series of Fortunate Events - PHP Benelux Conference 2015

Introducing a MediatorInstead of talking to the event handlers

Let's leave the talking to a mediator

Page 51: A Series of Fortunate Events - PHP Benelux Conference 2015

Before

LoggingEventHandler::handle()

NotificationMailEventHandler::handle()

PostService

Page 52: A Series of Fortunate Events - PHP Benelux Conference 2015

After

LoggingEventHandler::handle()

NotificationMailEventHandler::handle()

EventDispatcherPostService

Page 53: A Series of Fortunate Events - PHP Benelux Conference 2015

In codeclass PostService{ function __construct(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; }

function newCommentAdded($postId, $comment) { $event = new CommentAddedEvent($postId, $comment);

$this->dispatcher->dispatch( 'comment_added', $event ); }}

Page 54: A Series of Fortunate Events - PHP Benelux Conference 2015

Event class

use Symfony\Component\EventDispatcher\Event;

class CommentAddedEvent extends Event{ ...}

Custom event classes should extend Symfony Event class:

Page 55: A Series of Fortunate Events - PHP Benelux Conference 2015

Configurationuse Symfony\Component\EventDispatcher\Event;

$dispatcher = new EventDispatcher();

$loggingEventHandler = new LoggingEventHandler($logger);

$dispatcher->addListener( 'comment_added', array($loggingEventHandler, 'handle'));...

$postService = new PostService($dispatcher);

Page 56: A Series of Fortunate Events - PHP Benelux Conference 2015

Symfony2● An event dispatcher is available as the event_dispatcher service

● You can register event listeners using service tags

Page 57: A Series of Fortunate Events - PHP Benelux Conference 2015

Inject the event dispatcher# services.yml

services:

post_service:class: PostServicearguments: [@event_dispatcher]

Page 58: A Series of Fortunate Events - PHP Benelux Conference 2015

Register your listeners# services.yml

services: ...

logging_event_handler:class: LoggingEventHandlerarguments: [@logger]tags:

- { name: kernel.event_listenerevent: comment_addedmethod: handle

}

Page 59: A Series of Fortunate Events - PHP Benelux Conference 2015

Events and application flowSymfony2 uses events to generate response for any given HTTP request

Page 60: A Series of Fortunate Events - PHP Benelux Conference 2015

The HttpKernel$request = Request::createFromGlobals();

// $kernel is in an instance of HttpKernelInterface

$response = $kernel->handle($request);

$response->send();

Page 61: A Series of Fortunate Events - PHP Benelux Conference 2015

Kernel events

Page 62: A Series of Fortunate Events - PHP Benelux Conference 2015

kernel.request● Route matching

● Authentication

Page 63: A Series of Fortunate Events - PHP Benelux Conference 2015

kernel.controller

● Replace the controller

● Do some access checks

Page 64: A Series of Fortunate Events - PHP Benelux Conference 2015

kernel.response

● Modify the response

● E.g. inject the Symfony toolbar

Page 65: A Series of Fortunate Events - PHP Benelux Conference 2015

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

Page 66: A Series of Fortunate Events - PHP Benelux Conference 2015

Chain of responsibility

Handler 3Handler 1 Handler 2

Some sort of request

Some sort of request

Response

Some sort of request

Page 67: A Series of Fortunate Events - PHP Benelux Conference 2015

Symfony example

Listener 3Listener 1 Listener 2

Exception! Exception!

Response

I've got an exception! What should I tell the user?

Page 68: A Series of Fortunate Events - PHP Benelux Conference 2015

Propagationclass HandleExceptionListener{

function onKernelException(GetResponseForExceptionEvent $event

) {$event->setResponse(new Response('Error!'));

// this is the best response ever, don't let// others spoil it!

$event->stopPropagation();}

}

Page 69: A Series of Fortunate Events - PHP Benelux Conference 2015

Priorities$dispatcher = new EventDispatcher();

$dispatcher->addListener( 'comment_added', array($object, $method), // priority 100);

Page 70: A Series of Fortunate Events - PHP Benelux Conference 2015

Concerns

Page 71: A Series of Fortunate Events - PHP Benelux Conference 2015

Concern 1: Hard to understand“Click-through understanding” impossible

$event = new CommentAddedEvent($postId, $comment);

$this->dispatcher->dispatch('comment_added',$event

);

Page 72: A Series of Fortunate Events - PHP Benelux Conference 2015

interface EventDispatcherInterface{ function dispatch($eventName, Event $event = null);

...}

Page 73: A Series of Fortunate Events - PHP Benelux Conference 2015

SolutionUse Xdebug

Page 74: A Series of Fortunate Events - PHP Benelux Conference 2015

Concern 2: Out-of-domain concepts● “Comment”

● “PostId”

● “Add comment to post”

● “Dispatcher” (?!)

Page 75: A Series of Fortunate Events - PHP Benelux Conference 2015

We did a good thing

We fixed coupling issues

Page 76: A Series of Fortunate Events - PHP Benelux Conference 2015

She's called Cohesion

But this guy, Coupling, has a sister

Page 77: A Series of Fortunate Events - PHP Benelux Conference 2015

Cohesion● Belonging together

● Concepts like “dispatcher”, “event listener”, even “event”, don't belong in your code

Page 78: A Series of Fortunate Events - PHP Benelux Conference 2015

Solutions (1)Descriptive, explicit naming:

● NotificationMailEventListener becomes SendNotificationMailWhenCommentAdded

● CommentAddedEvent becomes CommentAdded

● onCommentAdded becomes whenCommentAdded

Page 79: A Series of Fortunate Events - PHP Benelux Conference 2015

Solutions (1)

This also hides implementation details!

Page 80: A Series of Fortunate Events - PHP Benelux Conference 2015

Solutions (2)Use an event dispatcher for things

that are not naturally cohesive anyway

Page 81: A Series of Fortunate Events - PHP Benelux Conference 2015

Solutions (2)Use something else

when an event dispatcher causes low cohesion

Page 82: A Series of Fortunate Events - PHP Benelux Conference 2015

Example: resolving the controller

$event = new GetResponseEvent($request);

$dispatcher->dispatch('kernel.request', $event);

$controller = $request->attributes->get('_controller');

$controller = $controllerResolver->resolve($request);

Page 83: A Series of Fortunate Events - PHP Benelux Conference 2015

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?

Page 84: A Series of Fortunate Events - PHP Benelux Conference 2015

Solution● “Won't fix”

● You have to learn to live with it

Page 85: A Series of Fortunate Events - PHP Benelux Conference 2015

It's good

Page 86: A Series of Fortunate Events - PHP Benelux Conference 2015

Inversion of control

exercise control

give up control!

Page 87: A Series of Fortunate Events - PHP Benelux Conference 2015

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

Page 88: A Series of Fortunate Events - PHP Benelux Conference 2015

I admit, inversion of control can be scary

Page 89: A Series of Fortunate Events - PHP Benelux Conference 2015

But it will● lead to better design

● require less change

● make maintenance easier

Page 90: A Series of Fortunate Events - PHP Benelux Conference 2015

PresentationFinishedAskQuestionsWhenPresentationFinished

SayThankYouWhenNoMoreQuestions

Page 91: A Series of Fortunate Events - PHP Benelux Conference 2015

Class and package design principles

http://leanpub.com/principles-of-package-design/c/phpbnl15

Get it for $ 19

Page 92: A Series of Fortunate Events - PHP Benelux Conference 2015

Design patterns● Observer

● Mediator

● Chain of responsibility

● ...

Design Patterns by “The Gang of Four”

Page 93: A Series of Fortunate Events - PHP Benelux Conference 2015

SOLID principles● Single responsibility

● Open/closed

● Dependency inversion

● ...

Agile Software Development by Robert C. Martin

Page 94: A Series of Fortunate Events - PHP Benelux Conference 2015

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/

Page 95: A Series of Fortunate Events - PHP Benelux Conference 2015

Twitter: @matthiasnoback

joind.in/13120

What did you think?