a series of fortunate events - php benelux conference 2015

Post on 26-Jul-2015

464 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

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 to start talking

Events in software Events model what happened in a system

Other parts of the system can respond to what happened

Imperative programmingOnly commands

doThis();

doThat();

updateSomething($something);

return $something;

Extracting eventsdoThis();// this was done

doThat();// that was done

updateSomething($something)// something was updated

return $something;

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

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

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

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

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

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 patternNotify other parts of the application when a change occurs

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

Observer contract

interface Observer{ function notify();}

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

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

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

Concrete observers

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

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

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

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

Before

PostServicePostService

Mailer

Logger

After

NotificationMailObserver

Observer

Observer

LoggingObserver

Mailer

Logger

PostService

Design Principles Party

Single responsibilityEach class has one small,

well-defined responsibility

Single responsibility● PostService:

“add comments to posts”

● LoggingObserver: “write a line to the log”

● NotificationMailObserver: “send a notification mail”

Single responsibilityWhen 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 timestamp to the notification mail!”: NotificationMailObserver

Dependency inversionDepend on abstractions, not on concretions

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

Mailer

LoggerPostService

Dependency inversionNow it depends on something abstract: an Observer

Observer

Observer

PostService

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

NotificationMailObserver

LoggingObserver

Mailer

Logger

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

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

Observer

Observer Observer

PostService

Open/closedWe 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 $this->logger->info('New comment'); }}

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

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

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

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 handlersclass LoggingEventHandler implements CommentAddedEventHandler{ function __construct(Logger $logger) { $this->logger = $logger; }

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

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

Configuration

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

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

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

Introducing a MediatorInstead of talking to the event handlers

Let's leave the talking to a mediator

Before

LoggingEventHandler::handle()

NotificationMailEventHandler::handle()

PostService

After

LoggingEventHandler::handle()

NotificationMailEventHandler::handle()

EventDispatcherPostService

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

Event class

use Symfony\Component\EventDispatcher\Event;

class CommentAddedEvent extends Event{ ...}

Custom event classes should extend Symfony Event class:

Configurationuse Symfony\Component\EventDispatcher\Event;

$dispatcher = new EventDispatcher();

$loggingEventHandler = new LoggingEventHandler($logger);

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

$postService = new PostService($dispatcher);

Symfony2● An event dispatcher is available as the event_dispatcher service

● You can register event listeners using service tags

Inject the event dispatcher# services.yml

services:

post_service:class: PostServicearguments: [@event_dispatcher]

Register your listeners# services.yml

services: ...

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

- { name: kernel.event_listenerevent: comment_addedmethod: handle

}

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

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

// $kernel is in an instance of HttpKernelInterface

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

$response->send();

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 in and modify or override behavior

Chain of responsibility

Handler 3Handler 1 Handler 2

Some sort of request

Some sort of request

Response

Some sort of request

Symfony example

Listener 3Listener 1 Listener 2

Exception! Exception!

Response

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

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

}

Priorities$dispatcher = new EventDispatcher();

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

Concerns

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

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

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

);

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

...}

SolutionUse 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:

● NotificationMailEventListener becomes SendNotificationMailWhenCommentAdded

● CommentAddedEvent becomes CommentAdded

● onCommentAdded becomes whenCommentAdded

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

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

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

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?

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

● And when you die, someone will bury your body for you

I admit, inversion of control can be scary

But it will● lead to better design

● require less change

● make maintenance easier

PresentationFinishedAskQuestionsWhenPresentationFinished

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. Martin

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/

Twitter: @matthiasnoback

joind.in/13120

What did you think?

top related