Slide 1

Slide 1 text

The Action-Domain-Responder Design Pattern

Slide 2

Slide 2 text

Code's over here: https://github.com/tobiasgies/adr-phpnw14

Slide 3

Slide 3 text

Design Pattern?

Slide 4

Slide 4 text

Design Pattern!

Slide 5

Slide 5 text

A look at MVC ● Separates concerns of desktop app ● Interactive environment ● Event-driven ● OLDER THAN ME! (Says the 23-year-old)

Slide 6

Slide 6 text

A look at MVC Controller Model View

Slide 7

Slide 7 text

Actually... Controller Model View Controller Model View Controller Model View Controller Model View Controller Model View Controller Model View Controller Model View

Slide 8

Slide 8 text

MVC on the web Controller Model View SYSTEM BO UN DARY

Slide 9

Slide 9 text

Let's refine this.

Slide 10

Slide 10 text

Web-specific ● We are on a server ● In-memory desktop patterns won't help us ● Use existing ideas as a basis ● Refine towards better practices

Slide 11

Slide 11 text

From Model to Domain ● Essentially identical responsibilities: – Communicate with data source – Encapsulate business rules ● Renamed to reflect more modern concepts: – Domain Logic – Domain Driven Design

Slide 12

Slide 12 text

A controller does too much. ● Controller's methods: – indexAction – createAction – editAction – deleteAction – … ● Dependency Injection nightmare ● Single Responsibility Principle?!

Slide 13

Slide 13 text

An action method does too much. ● An action method's job: – Make sense of input – Pass input to Model – Check model's results – Figure out which response to send – Prepare and return response – … ● Single Responsibility Principle?!

Slide 14

Slide 14 text

From Controller to Action ● One class per Action ● Sharper focus: – Make sense of input – Pass input to Domain – Pass Domain's results to Responder

Slide 15

Slide 15 text

A View is not enough ● A view's task: – Accept application data – Build output from data and templates ● View does not take care of: – Deciding which data type to output – Sending neccessary HTTP headers ● View is Response, we need a Responder

Slide 16

Slide 16 text

From View to Responder ● Sends HTTP headers ● Assembles output ● Returns Response

Slide 17

Slide 17 text

Components ● Domain: The logic to manipulate data and application state, based on your business rules. ● Responder: Handles everything connected to building an HTTP response. Headers, cookies, status codes and response content are built here. ● Action: Connects the Domain and Responder. Passes input data to Domain, passes Domain output to Responder.

Slide 18

Slide 18 text

Enough talking, let's look at the code.

Slide 19

Slide 19 text

class BlogController { private $request; private $response; private $model; private $view; public function __construct( Request $request, Response $response, BlogRepository $model, TemplateSystem $view ) { /* ... */ } public function indexAction() { /* ... */ } public function createAction() { if ($this->request->isPost()) { $data = $this->request->getParameters(Request::SOURCE_POST); $post = $this->model->createPost($data); if ($post->isValid()) { $post->save(); $this->response->redirect('/blog/edit/' . $post->getId()); } else { $this->response->setContent( $this->view->render('create.tpl', array('post' => $post)) ); } } else { $this->response->setContent( $this->view->render('create.tpl', array('blog' => $this->model->getDefault())) ); } } public function editAction() { /* ... */ } public function deleteAction() { /* ... */ } }

Slide 20

Slide 20 text

public function createAction() { if ($this->request->isPost()) { $data = $this->request->getParameters(Request::SOURCE_POST); $post = $this->model->createPost($data); if ($post->isValid()) { $post->save(); $this->response->redirect('/blog/edit/' . $post->getId()); } else { $this->response->setContent( $this->view->render( 'create.tpl', array('post' => $post) ) ); } } else { $this->response->setContent( $this->view->render( 'create.tpl', array('blog' => $this->model->getDefault()) ) ); } }

Slide 21

Slide 21 text

Let's refactor this.

Slide 22

Slide 22 text

class BlogCreateAction { private $request; private $domain; private $responder; public function __construct( Request $request, BlogRepository $domain, BlogCreateResponder $responder ) { /* ... */ } public function __invoke() { if ($this->request->isPost()) { $data = $this->request->getParameters( Request::SOURCE_POST ); $post = $this->domain->createPost($data); if ($post->isValid()) { $post->save(); } } else { $post = $this->domain->getDefault(); } $this->responder->__invoke($post); } }

Slide 23

Slide 23 text

class BlogCreateResponder { private $response; private $templateSystem; public function __construct( Response $response, TemplateSystem $templateSystem ) { /* ... */ } public function __invoke(BlogPost $post) { if (!empty($post->getId())) { $this->response->redirect('/blog/edit/' . $post->getId()); } else { $this->response->setContent( $this->templateSystem->render( 'create.tpl', array('post' => $post) ) ); } } }

Slide 24

Slide 24 text

Can we clean up further?

Slide 25

Slide 25 text

class BlogAddAction { private $domain; private $responder; public function __construct( BlogRepository $domain, BlogCreateResponder $responder ) { /* ... */ } public function __invoke() { $post = $this->domain->getDefault(); $this->responder->__invoke($post); } }

Slide 26

Slide 26 text

class BlogCreateAction { private $request; private $domain; private $responder; public function __construct( Request $request, BlogRepository $domain, BlogCreateResponder $responder ) { /* ... */ } public function __invoke() { try { $data = $this->request->getParameters( Request::SOURCE_POST ); $post = $this->domain->createPost($data); } catch (BlogException $ex) { $post = $this->domain->getDefault(); } $this->responder->__invoke($post); } }

Slide 27

Slide 27 text

class BlogCreateResponder { private $response; private $templateSystem; public function __construct( Response $response, TemplateSystem $templateSystem ) { /* ... */ } public function __invoke(BlogPost $post) { if (!empty($post->getId())) { $this->response->redirect('/blog/edit/' . $post->getId()); } else { $this->response->setContent( $this->templateSystem->render( 'create.tpl', array('post' => $post) ) ); } } }

Slide 28

Slide 28 text

Action-Domain-Responder

Slide 29

Slide 29 text

Any questions?

Slide 30

Slide 30 text

Thank you! [email protected] - @tobiasgies https://joind.in/11806