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

Action-Domain-Responder - PHPNW14

Action-Domain-Responder - PHPNW14

The slides for my talk about the Action-Domain-Responder design pattern at PHP Northwest 2014. If you were at my talk, please rate it: https://joind.in/11806

The example code I used in this slide deck is also on github: https://github.com/tobiasgies/adr-phpnw14

503778aa6a31b4ecb5b37ffb62ff5dab?s=128

Tobias Gies

October 05, 2014
Tweet

Transcript

  1. The Action-Domain-Responder Design Pattern

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

  3. Design Pattern?

  4. Design Pattern!

  5. A look at MVC • Separates concerns of desktop app

    • Interactive environment • Event-driven • OLDER THAN ME! (Says the 23-year-old)
  6. A look at MVC Controller Model View

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

    Controller Model View Controller Model View Controller Model View Controller Model View
  8. MVC on the web Controller Model View SYSTEM BO UN

    DARY
  9. Let's refine this.

  10. 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
  11. 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
  12. A controller does too much. • Controller's methods: – indexAction

    – createAction – editAction – deleteAction – … • Dependency Injection nightmare • Single Responsibility Principle?!
  13. 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?!
  14. From Controller to Action • One class per Action •

    Sharper focus: – Make sense of input – Pass input to Domain – Pass Domain's results to Responder
  15. 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
  16. From View to Responder • Sends HTTP headers • Assembles

    output • Returns Response
  17. 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.
  18. Enough talking, let's look at the code.

  19. 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() { /* ... */ } }
  20. 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()) ) ); } }
  21. Let's refactor this.

  22. 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); } }
  23. 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) ) ); } } }
  24. Can we clean up further?

  25. class BlogAddAction { private $domain; private $responder; public function __construct(

    BlogRepository $domain, BlogCreateResponder $responder ) { /* ... */ } public function __invoke() { $post = $this->domain->getDefault(); $this->responder->__invoke($post); } }
  26. 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); } }
  27. 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) ) ); } } }
  28. Action-Domain-Responder

  29. Any questions?

  30. Thank you! tobias@tobiasgies.de - @tobiasgies https://joind.in/11806