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

Tobias Gies

October 05, 2014
Tweet

More Decks by Tobias Gies

Other Decks in Programming

Transcript

  1. The Action-Domain-Responder
    Design Pattern

    View Slide

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

    View Slide

  3. Design Pattern?

    View Slide

  4. Design Pattern!

    View Slide

  5. A look at MVC

    Separates concerns
    of desktop app

    Interactive
    environment

    Event-driven

    OLDER THAN ME!
    (Says the 23-year-old)

    View Slide

  6. A look at MVC
    Controller
    Model
    View

    View Slide

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

    View Slide

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

    View Slide

  9. Let's refine this.

    View Slide

  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

    View Slide

  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

    View Slide

  12. A controller does too much.

    Controller's methods:
    – indexAction
    – createAction
    – editAction
    – deleteAction
    – …

    Dependency Injection nightmare

    Single Responsibility Principle?!

    View Slide

  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?!

    View Slide

  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

    View Slide

  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

    View Slide

  16. From View to Responder

    Sends HTTP headers

    Assembles output

    Returns Response

    View Slide

  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.

    View Slide

  18. Enough talking,
    let's look at the code.

    View Slide

  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() { /* ... */ }
    }

    View Slide

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

    View Slide

  21. Let's refactor this.

    View Slide

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

    View Slide

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

    View Slide

  24. Can we clean up further?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  28. Action-Domain-Responder

    View Slide

  29. Any questions?

    View Slide

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

    View Slide