$30 off During Our Annual Pro Sale. View Details »

Decoupling from the framework

Decoupling from the framework

A framework, by definition, provides a basic set of tools for application development we can use to avoid writing repeatable code. It often encourages to take shortcuts to enable rapid development. In theory, we only need to implement the part which is specific to our domain. In practice, we often end up with highly coupled code, mixed layers and a dependency graph deceptively close to spaghetti.
Jakub will take Symfony as an example to present techniques of decoupling from a framework and keeping it on a distance.

Jakub Zalas

April 07, 2014
Tweet

More Decks by Jakub Zalas

Other Decks in Programming

Transcript

  1. Decoupling from the
    framework
    Jakub Zalas
    7th of April 2014

    View Slide

  2. @jakzal
    @jakub_zalas
    @SensioLabsUK
    £  

    View Slide

  3. COUPLING

    View Slide

  4. "MODULES ARE COUPLED
    IF CHANGING ONE OF THEM
    REQUIRES CHANGING ANOTHER
    ONE."
    http://martinfowler.com/ieeeSoftware/coupling.pdf
    Martin Fowler

    View Slide

  5. tight
    coupling
    loose
    coupling

    View Slide

  6. COUPLING AND COHESION

    View Slide

  7. tight
    coupling
    low
    cohesion

    View Slide

  8. loose
    coupling
    high
    cohesion

    View Slide

  9. WHY IS
    TIGHT
    COUPLING
    BAD?

    View Slide

  10. WHY IS
    LOOSE
    COUPLING
    GOOD?

    View Slide

  11. REUSABILITY

    View Slide

  12. READABILITY

    View Slide

  13. MAINTENANCE

    View Slide

  14. CHANGE

    View Slide

  15. LOOSEN UP?
    How to

    View Slide

  16. MAKE
    DEPENDENCIES
    EXPLICIT

    View Slide

  17. class PackageCrawler
    {
    public function crawl($resource)
    {
    $browser = Browser::getInstance();
    $response = $browser->get($resource);
    // @todo parse
    }
    }

    View Slide

  18. use Buzz\Browser;
    class PackageCrawler
    {
    public function crawl($resource)
    {
    $browser = new Browser();
    $response = $browser->get($resource);
    // @todo parse
    }
    }

    View Slide

  19. DEPENDENCY
    INJECTION !

    View Slide

  20. use Buzz\Browser;
    class PackageCrawler
    {
    private $browser;
    public function __construct(Browser $browser)
    {
    $this->browser = $browser;
    }
    public function crawl($resource)
    {
    $response = $this->browser->get($resource);
    // @todo parse
    }
    }

    View Slide

  21. INJECT
    EXACTLY
    WHAT YOU
    NEED

    View Slide

  22. use Buzz\Browser;
    class PackageCrawler
    {
    private $browser;
    public function __construct(Browser $browser)
    {
    $this->browser = $browser;
    }
    public function crawl($resource, Container $container)
    {
    $user = $container->get('security.context')
    ->getToken()->getUser();
    $response = $this->browser->get(
    $resource,
    array('X-User' => $user->getId())
    );
    // @todo parse
    }
    }

    View Slide

  23. https://twitter.com/iamtankist/status/359382451816112129

    View Slide

  24. use Buzz\Browser;
    class PackageCrawler
    {
    private $browser;
    public function __construct(Browser $browser)
    {
    $this->browser = $browser;
    }
    public function crawl($resource, $userId)
    {
    $response = $this->browser->get(
    $resource,
    array('X-User' => $userId)
    );
    // @todo parse
    }
    }

    View Slide

  25. DON'T MIX
    LAYERS

    View Slide

  26. use Buzz\Browser;
    use Symfony\Component\Console\Input\InputInterface;
    class PackageCrawler
    {
    private $browser;
    public function __construct(Browser $browser)
    {
    $this->browser = $browser;
    }
    public function crawl(InputInterface $input)
    {
    $response = $this->browser->get(
    $input->getOption('resource')
    );
    // @todo parse
    }
    }

    View Slide

  27. use Buzz\Browser;
    class PackageCrawler
    {
    private $browser;
    public function __construct(Browser $browser)
    {
    $this->browser = $browser;
    }
    public function crawl($resource)
    {
    $response = $this->browser->get(
    $resource
    );
    // @todo parse
    }
    }

    View Slide

  28. BI-DIRECTIONAL
    DEPENDENCIES
    ARE A SMELL

    View Slide

  29. BREAK
    DEPENDENCIES

    View Slide

  30. use Buzz\Browser;
    class PackageCrawler
    {
    private $browser;
    public function __construct(Browser $browser)
    {
    $this->browser = $browser;
    }
    public function crawl($resource)
    {
    $response = $this->browser->get($resource);
    // @todo parse
    }
    }

    View Slide

  31. View Slide

  32. View Slide

  33. interface HttpClient
    {
    public function get($resource, array $headers = []);
    }

    View Slide

  34. class PackageCrawler
    {
    private $httpClient;
    public function __construct(HttpClient $httpClient)
    {
    $this->httpClient = $httpClient;
    }
    public function crawl($resource)
    {
    $response = $this->httpClient->get($resource);
    // @todo parse
    }
    }

    View Slide

  35. View Slide

  36. View Slide

  37. use Buzz\Browser;
    class BuzzClient implements HttpClient
    {
    private $browser;
    public function __construct(Browser $browser)
    {
    $this->browser = $browser;
    }
    public function get($resource, array $headers)
    {
    return (string) $this->browser->get(
    $resource, $headers
    );
    }
    }

    View Slide

  38. use Guzzle\Client;
    class GuzzleClient implements HttpClient
    {
    private $guzzle;
    public function __construct(Client $guzzle)
    {
    $this->guzzle = $guzzle;
    }
    public function get($resource, array $headers)
    {
    $request = $this->guzzle->createRequest(
    'GET', $resource
    );
    $response = $request->send();
    return $response->getBody();
    }
    }

    View Slide

  39. DEFER
    DECISIONS

    View Slide

  40. DEFER
    COMMITMENT

    View Slide

  41. FRAMEWORK?

    View Slide

  42. use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    class SearchController extends Controller
    {
    public function searchAction(Request $request)
    {
    $keywords = $request->query->get('keywords');
    $products = $this->getDoctrine()
    ->getRepository('Acme:Product')
    ->search($keywords);
    return $this->render(
    'template.html.twig',
    array('products' => $products)
    );
    }
    }

    View Slide

  43. LAYERED
    ARCHITECTURE

    View Slide

  44. https://www.goodreads.com/book/show/179133.Domain_driven_Design

    View Slide

  45. CLEAN
    ARCHITECTURE

    View Slide

  46. http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

    View Slide

  47. SOURCE CODE
    DEPENDENCIES
    CAN ONLY POINT
    INWARDS

    View Slide

  48. HEXAGONAL
    ARCHITECTURE
    a.k.a.
    PORTS AND
    ADAPTERS

    View Slide

  49. Application
    Doctrine
    In
    memory
    UI
    Tests
    CLI

    View Slide

  50. "Create your application to
    work without either a
    UI or a database
    […]"
    http://alistair.cockburn.us/Hexagonal+architecture

    View Slide

  51. UI
    IS AN
    IMPLEMENTATION
    DETAIL

    View Slide

  52. DATABASE
    IS AN
    IMPLEMENTATION
    DETAIL

    View Slide

  53. FRAMEWORK
    IS AN
    IMPLEMENTATION
    DETAIL

    View Slide

  54. View Slide

  55. STEP 0
    MAKE
    DEPENDENCIES
    EXPLICIT

    View Slide

  56. use Doctrine\Common\Persistence\ManagerRegistry;
    use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
    class SearchController
    {
    private $templating;
    private $doctrine;
    public function __construct(
    EngineInterface $templating,
    ManagerRegistry $doctrine
    ) {
    $this->templating = $templating;
    $this->doctrine = $doctrine;
    }
    }

    View Slide

  57. // ..
    class SearchController
    {
    // ...
    public function searchAction(Request $request)
    {
    $keywords = $request->query->get('keywords');
    $repository = $this->doctrine
    ->getRepository('Acme:Product')
    ->search($keywords);
    return $this->templating->renderResponse(
    'template.html.twig',
    array('products' => $products)
    );
    }
    }

    View Slide

  58. STEP 1
    ONLY INJECT WHAT
    YOU ACTUALLY
    NEED

    View Slide

  59. use Doctrine\Common\Persistence\ManagerRegistry;
    use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
    class SearchController
    {
    private $templating;
    private $doctrine;
    public function __construct(
    EngineInterface $templating,
    ManagerRegistry $doctrine
    ) {
    $this->templating = $templating;
    $this->doctrine = $doctrine;
    }
    }

    View Slide

  60. use Acme\ProductBundle\Entity\ProductRepository;
    use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
    class SearchController
    {
    private $templating;
    private $repository;
    public function __construct(
    EngineInterface $templating,
    ProductRepository $repository
    ) {
    $this->templating = $templating;
    $this->repository = $repository;
    }
    }

    View Slide

  61. // ..
    class SearchController
    {
    // ...
    public function searchAction(Request $request)
    {
    $keywords = $request->query->get('keywords');
    $repository = $this->repository->search($keywords);
    return $this->templating->renderResponse(
    'template.html.twig',
    array('products' => $products)
    );
    }
    }

    View Slide

  62. STEP 2
    DON'T TALK TO THE
    INFRASTRUCTURE
    DIRECTLY

    View Slide

  63. namespace Acme\ProductCatalog;
    interface ProductRepository
    {
    /**
    * @param string $keywords
    *
    * @return Product[]
    */
    public function search($keywords);
    }

    View Slide

  64. use Acme\ProductCatalog\ProductRepository;
    use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
    class SearchController
    {
    private $templating;
    private $repository;
    public function __construct(
    EngineInterface $templating,
    ProductRepository $repository
    ) {
    $this->templating = $templating;
    $this->repository = $repository;
    }
    }

    View Slide

  65. View Slide

  66. DON'T FOLLOW
    THE FRAMEWORK

    View Slide

  67. $this->getDoctrine()
    ->getRepository('Acme:product')
    ->find($keywords);

    View Slide

  68. MAKE IT
    OPTIONAL

    View Slide

  69. A GOOD
    FRAMEWORK
    WILL HELP YOU

    View Slide

  70. $this->repository->search($keywords);
    class="Acme\ProductCatalog\ProductRepository"
    factory-service="doctrine"
    factory-method="getRepository">
    Acme:Product

    View Slide

  71. DON'T START
    WITH
    THE FRAMEWORK

    View Slide

  72. MAKE YOUR
    APPLICATION
    INDEPENDENT

    View Slide

  73. AND THEN
    INTEGRATE IT
    WITH
    THE FRAMEWORK

    View Slide

  74. REMEMBER!

    View Slide

  75. TDD
    IS YOUR
    FRIEND

    View Slide

  76. THANK YOU!

    View Slide

  77. Credits

    View Slide