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

Applying Domain Driven Design with Symfony

Applying Domain Driven Design with Symfony

When we learn Symfony2 from the documentation, it provides us with easy and convenient ways of getting the job done. Over time our projects grow and this early convenience can become an obstacle. In this session Marek will focus on how to make our code less dependent on Symfony2 and more domain focused.

Marek Matulka

April 16, 2014
Tweet

More Decks by Marek Matulka

Other Decks in Programming

Transcript

  1. Typical Symfony project... … starts with intention to write: *

    high quality code * reusable code * portable code * code you’re proud of
  2. Typical Symfony controller... use Symfony\Bundle\FrameworkBundle\Controller\Controller; class RegistrationController extends Controller {

    public function registerAction($id) { $conference = $this->getDoctrine() ->getRepository(‘AcmeConferenceBundle:Conference’) ->find($id); if ($this->get(‘acme_conference.enrolment.service’) ->enrol($conference, $this->getUser())) { $this->get(‘acme_conference.notification.service’) ->notify($this->getUser(), ‘success’); } } }
  3. Typical Symfony controller... Dependency Injection * used as a service

    locator Fat controllers * with actions full of business logic
  4. Typical Symfony project... ...ends full of: * tightly coupled objects

    * objects that are difficult to re-use * code you’re afraid to change * code you cannot test
  5. Why it ended like that? * poor understanding of the

    core domain * bad names * urge to use framework’s goodies * ContainerAware is evil * locked into framework’s structure * Acme\Bundle\TestBundle\Entity
  6. namespace Acme\ConferenceBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class RegistrationController extends Controller { public

    function registerAction($id) { $conference = $this->getDoctrine() ->getRepository(‘AcmeConferenceBundle:Conference’) ->find($id); if ($this->get(‘acme_conference.enrolment.service’) ->enrol($conference, $this->getUser())) { $this->get(‘acme_conference.notification.service’) ->notify($this->getUser(), ‘success’); $this->redirect(‘registration_successful’) } $this->redirect(‘registration_failed’); } }
  7. namespace Acme\ConferenceBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class RegistrationController extends Controller { public

    function registerAction($id) { $conference = $this->getDoctrine() ->getRepository(‘AcmeConferenceBundle:Conference’) ->find($id); if ($this->get(‘acme_conference.enrolment.service’) ->enrol($conference, $this->getUser())) { $this->get(‘acme_conference.notification.service’) ->notify($this->getUser(), ‘success’); return $this->redirect(‘registration_successful’); } return $this->redirect(‘registration_failed’); } }
  8. namespace Acme\ConferenceBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class RegistrationController extends Controller { public

    function registerAction($id) { $conference = $this->getDoctrine() ->getRepository(‘AcmeConferenceBundle:Conference’) ->find($id); if ($this->get(‘acme_conference.enrolment.service’) ->enrol($conference, $this->getUser())) { $this->get(‘acme_conference.notification.service’) ->notify($this->getUser(), ‘success’); return $this->redirect(‘registration_successful’); } return $this->redirect(‘registration_failed’); } } Repository Response Service Service Value
  9. namespace Acme\ConferenceBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class RegistrationController extends Controller { public

    function registerAction($id) { $conference = $this->getDoctrine() ->getRepository(‘AcmeConferenceBundle:Conference’) ->find($id); if ($this->get(‘acme_conference.enrolment.service’) ->enrol($conference, $this->getUser())) { $this->get(‘acme_conference.notification.service’) ->notify($this->getUser(), ‘success’); return $this->redirect(‘registration_successful’); } return $this->redirect(‘registration_failed’); } } ConferenceRepository RedirectResponse ConferenceEnrolment EnrolmentNotification Conference
  10. namespace Acme\ConferenceBundle\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Acme\Learning\Conference; class RegistrationController

    { /** * @Route("/conference/{id}/enrol") * @ParamConverter("conference", class="AcmeConferenceBundle:Conference") */ public function registerAction(Conference $conference) { if ($this->get(‘acme_conference.enrolment.service’) ->enrol($conference, $this->getUser())) { $this->get(‘acme_conference.notification.service’) ->notify($this->getUser(), ‘success’); return $this->redirect(‘registration_successful’); } return $this->redirect(‘registration_failed’); } }
  11. namespace Acme\ConferenceBundle\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Acme\Enrolment\ConferceEnrolment; class RegistrationController

    { public function __construct(ConferceEnrolment $enrolment) { $this->enrolment = $enrolment; } /** * @Route("/conference/{id}/enrol") * @ParamConverter("conference", class="AcmeConferenceBundle:Conference") */ public function registerAction(Conference $conference) { if ($this->enrolment->enrol($conference, $this->getUser())) { ... } } }
  12. namespace Acme\ConferenceBundle\Controller; use Acme\Enrolment\ConferceEnrolment; class RegistrationController { public function __construct(ConferceEnrolment

    $enrolment) { $this->enrolment = $enrolment; } public function registerAction(Conference $conference) { $conference = $this->repository->find($id); if ($this->enrolment->enrol($conference, $this->getUser())) { ... } } } $this->container->get(‘security.context’) ->getToken()->getUser();
  13. # src/Acme/ConferenceBundle/Resources/config/services.xml <service id="symfony.security.auth.token" class="%symfony.security.auth.token.class%" factory-service="security.context" factory-method="getToken"> </service> <service id="acme.learner"

    class="%inviqa.learner.class%" factory-service="symfony.security.auth.token" factory-method="getUser"> </service> # src/Acme/Learning/Learner.php namespace Acme\Learning; interface Learner { public function isEnrolled(); }
  14. namespace Acme\ConferenceBundle\Controller; use Acme\Learning\Learner; use Acme\Enrolment\ConferceEnrolment; class RegistrationController { public

    function __construct( Learner $learner, ConferceEnrolment $enrolment ) { $this->learner = $learner; $this->enrolment = $enrolment; } public function registerAction(Conference $conference) { if ($this->enrolment->enrol($conference, $this->learner)) { ... } } }
  15. namespace Acme\ConferenceBundle\Controller; use Acme\Learning\Learner; use Acme\Enrolment\ConferceEnrolment; class RegistrationController { public

    function __construct( Learner $learner; ConferceEnrolment $enrolment ) { $this->learner = $learner; $this->enrolment = $enrolment; } public function registerAction(Conference $conference) { if ($this->enrolment->enrol($conference, $this->learner)) { ... } } } Interface
  16. namespace Acme\ConferenceBundle\Controller; use Acme\Learning\Learner; use Acme\Enrolment\ConferceEnrolment; use Symfony\Component\HttpFoundation\RedirectResponse; class RegistrationController

    { ... public function registerAction(Conference $conference) { if ($this->enrolment->enrol($conference, $this->learner)) { return new RedirectResponse(‘success’); } return new RedirectResponse(‘fail’); } }
  17. namespace Acme\ConferenceBundle\Controller; use Acme\Learning\Learner; use Acme\Enrolment\ConferceEnrolment; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Routing\RouterInterface;

    class RegistrationController { ... public function registerAction(Conference $conference) { if ($this->enrolment->enrol($conference, $this->learner)) { return new RedirectResponse( $this->router->generate(‘enrolment_successful’) ); } return new RedirectResponse( $this->router->generate(‘enrolment_failed’) ); } }
  18. namespace Acme\ConferenceBundle\Controller; ... use Acme\Enrolment\EnrolmentNotifier; class RegistrationController { ... public

    function registerAction(Conference $conference) { if ($this->enrolment->enrol($conference, $this->learner)) { $this->notifier->notify($this->learner, ‘success’); return new RedirectResponse( $this->router->generate(‘enrolment_successful’)); } return new RedirectResponse( $this->router->generate(‘enrolment_failed’)); } }
  19. namespace Acme\ConferenceBundle\Controller; use Acme\Learning\Learner; use Acme\Enrolment\ConferceEnrolment; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Routing\RouterInterface;

    use Acme\Enrolment\EnrolmentNotifier; class RegistrationController { public function __construct( Learner $learner, ConferenceEnrolment $enrolment, EnrolmentNotifier $notifier, RouterInterface $router ) { $this->learner = $learner; $this->enrolment = $enrolment; $this->notifier = $notifier; $this->router = $router; } ... Interfaces!
  20. namespace Acme\ConferenceBundle\Controller; use Acme\Learning\Learner; use Acme\Enrolment\ConferceEnrolment; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Routing\RouterInterface;

    use Acme\Enrolment\EnrolmentNotifier; class RegistrationController { public function __construct( Learner $learner ConferenceEnrolment $enrolment, EnrolmentNotifier $notifier, RouterInterface $router ) { $this->learner = $learner; $this->enrolment = $enrolment; $this->notifier = $notifier; $this->router = $router; } ... Interfaces!
  21. namespace Acme\Enrolment; use Acme\Learning\Learner; use Acme\Learning\Conference; use Acme\Enrolment\ConferceEnrolment; use Acme\Enrolment\EnrolmentNotifier;

    class ConferenceEnrolmentHandler { public function __construct( ConferceEnrolment $enrolment, EnrolmentNotifier $notifier ) { $this->enrolment = $enrolment; $this->notifier = $notifier; } public function handle(Conference $conference, Learner $learner) { if ($this->enrolment->handle($conference, $learner)) { $this->notifier->notify($learner, ‘success’); return true; } return false; } }
  22. namespace Acme\ConferenceBundle\Controller; use Acme\Learning\Learner; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Routing\RouterInterface; use Acme\Enrolment\ConferenceEnrolmentHandler;

    class RegistrationController { ... public function registerAction(Conference $conference) { $route = $this->handler->handle($conference, $this->learner) ? ‘enrolment_successful’ : ‘enrolment_failed’; return new RedirectResponse( $this->router->generate($route) ); } }
  23. # src/Acme/Conference/ConferenceRepository.php namespace Acme\Conference; interface ConferenceRepository { public function find($id);

    } # src/Acme/DoctrineAdapter/DoctrineConferenceAdapter.php namespace Acme\DoctrineAdapter; use Doctrine\ORM\EntityRepository; use Acme\Conference\ConferenceRepository; class DoctrineConferenceAdapter extends EntityRepository implements ConferenceRepository { // ConferenceRepository::find($id) is already implemented by EntityRepository } # src/Acme/DoctrineAdapter/Resources/mapping/conference.orm.xml <entity name="Acme\Conference\Conference" table="conferences" repository-class="Acme\DoctrineAdapter\ConferenceRepository">
  24. What is Domain Driven Design? Fully understanding your domain and

    describing your business process using the same language as your business.
  25. Dependency Inversion “High level modules should not depend on lower

    level implementation” – Good old Uncle Bob