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

The Dependency Trap

Jakub Zalas
September 26, 2014

The Dependency Trap

A framework, by definition, provides a basic set of tools to use for application development to avoid writing repeatable code. It often encourages us 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.

This talk is going back to basics to remind you what software coupling is and when to consider it good or bad. The presentation will also demonstrate a few techniques on how to write applications that embrace change in a Symfony environment.

Jakub Zalas

September 26, 2014
Tweet

More Decks by Jakub Zalas

Other Decks in Programming

Transcript

  1. The Dependency Trap
    Jakub Zalas

    View Slide

  2. @jakzal
    @jakub_zalas
    @SensioLabsUK
    £  

    View Slide

  3. LET'S WRITE A CRAWLER
    A random story

    View Slide

  4. Let's not reinvent the wheel
    {
    "require": {
    "kriswallsmith/buzz": "~0.12"
    }
    }

    View Slide

  5. 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

  6. View Slide

  7. View Slide

  8. WE NEED TO REPLACE THAT
    LIBRARY
    What if I told you…

    View Slide

  9. It's a trap!

    View Slide

  10. http://martinfowler.com/ieeeSoftware/coupling.pdf
    Martin Fowler
    "Modules are coupled
    if changing one of them
    requires changing another
    one."

    View Slide

  11. tight
    coupling
    loose
    coupling

    View Slide

  12. WHEN COUPLING INCREASES?

    View Slide

  13. A has an attribute that refers to B
    use Buzz\Browser;
    class Crawler
    {
    /**
    * @var Browser
    */
    private $browser;
    }

    View Slide

  14. A calls methods on B
    class Crawler
    {
    private $c;
    public function crawl($url)
    {
    $this->c->getBrowser()->get($url);
    }
    }

    View Slide

  15. A has a method that references B
    use Buzz\Browser;
    class Crawler
    {
    public function crawl($url, Browser $b)
    {
    $response = $b->get($url);
    }
    }

    View Slide

  16. A has a method that references B
    use Buzz\Browser;
    class Crawler
    {
    /**
    * @return Browser
    */
    public function crawl($url)
    {}
    }

    View Slide

  17. A extends or implements B
    use Buzz\Browser;
    class Crawler extends Browser
    {
    public function crawl($url)
    {
    $this->get($url);
    }
    }

    View Slide

  18. LET'S JUST DO IT OURSELVES
    If that's so bad

    View Slide

  19. class PackageCrawler
    {
    public function crawl($resource)
    {
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $resource);
    curl_setopt($curl, CURLOPT_HEADER, 0);
    $result = curl_exec($curl);
    curl_close($curl);
    // @todo parse $result
    }
    }

    View Slide

  20. Cohesion describes how
    closely are elements in a
    module related

    View Slide

  21. SINGLE RESPONSIBILITY
    PRINCIPLE ANYONE?
    A MODULE SHOULD HAVE ONE
    REASON TO CHANGE

    View Slide

  22. GATHER TOGETHER
    THE THINGS THAT
    CHANGE FOR
    THE SAME REASONS
    SEPARATE
    THOSE THINGS THAT
    CHANGE FOR
    DIFFERENT REASONS
    http://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html

    View Slide

  23. tight
    coupling
    low
    cohesion

    View Slide

  24. loose
    coupling
    high
    cohesion

    View Slide

  25. LOOSE COUPLING!
    Give us

    View Slide

  26. The Dependency-Inversion
    Principle
    High-level modules
    should not depend
    on low-level
    modules.
    High-level
    Low-level

    View Slide

  27. The Dependency-Inversion
    Principle
    High-level modules
    should not depend
    on low-level
    modules.
    Both should depend
    on abstractions.
    An abstraction!

    View Slide

  28. The Dependency-Inversion
    Principle
    Both should depend
    on abstractions.

    View Slide

  29. The Dependency-Inversion
    Principle
    Abstractions should
    not depend upon
    details.
    A detail!

    View Slide

  30. The Dependency-Inversion
    Principle
    Details should
    depend upon
    abstractions.

    View Slide

  31. interface ContentProvider
    {
    public function fetch($resource);
    }

    View Slide

  32. class PackageCrawler
    {
    private $contentProvider;
    public function __construct(ContentProvider $provider)
    {
    $this->contentProvider = $provider;
    }
    public function crawl($resource)
    {
    $response = $this->contentProvider->fetch($resource);
    // @todo parse
    }
    }

    View Slide

  33. View Slide

  34. use Buzz\Browser;
    class BuzzContentProvider implements ContentProvider
    {
    private $browser;
    public function __construct(Browser $browser)
    {
    $this->browser = $browser;
    }
    public function fetch($resource)
    {
    return (string) $this->browser->get(
    $resource, $headers
    );
    }
    }

    View Slide

  35. View Slide

  36. use Guzzle\Client;
    class GuzzleContentProvider implements ContentProvider
    {
    private $guzzle;
    public function __construct(Client $guzzle)
    {
    $this->guzzle = $guzzle;
    }
    public function fetch($resource)
    {
    $request = $this->guzzle->createRequest(
    'GET', $resource
    );
    $response = $request->send();
    return $response->getBody();
    }
    }

    View Slide

  37. View Slide

  38. View Slide

  39. WHY IS
    TIGHT
    COUPLING
    BAD?

    View Slide

  40. WHY IS
    LOOSE
    COUPLING
    GOOD?

    View Slide

  41. Decoupled code is
    • easier to read (understand)
    • easier to reuse
    • easier to maintain
    • easier to change

    View Slide

  42. ENABLE
    CHANGE!

    View Slide

  43. DEFER
    DECISIONS

    View Slide

  44. DEFER
    COMMITMENT

    View Slide

  45. FRAMEWORK?

    View Slide

  46. 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

  47. LAYERED
    ARCHITECTURE

    View Slide

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

    View Slide

  49. CLEAN
    ARCHITECTURE

    View Slide

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

    View Slide

  51. SOURCE CODE
    DEPENDENCIES
    CAN ONLY POINT
    INWARDS

    View Slide

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

    View Slide

  53. Application
    Doctrine
    In
    memory
    UI
    Tests
    CLI

    View Slide

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

    View Slide

  55. LET'S FIX IT
    just a little bit

    View Slide

  56. LET'S MAKE
    DEPENDENCIES
    EXPLICIT

    View Slide

  57. 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

  58. // ..
    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

  59. LET'S INJECT WHAT
    WE ACTUALLY NEED

    View Slide

  60. 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

  61. 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

  62. // ..
    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

  63. LET'S NOT TALK TO
    THE
    INFRASTRUCTURE
    DIRECTLY

    View Slide

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

    View Slide

  65. 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

  66. View Slide

  67. CHANGES
    ARE THE ONLY
    CONSTANT

    View Slide

  68. WE CANNOT
    PREDICT
    EVERY CHANGE

    View Slide

  69. WE CANNOT
    PREPARE FOR
    EVERY CHANGE

    View Slide

  70. PREMATURE
    ABSTRACTION
    IS EVIL

    View Slide

  71. DECIDING ON
    CHANGES
    IT'S WORTH TO
    PREPARE FOR
    IS A
    STRATEGIC
    DECISION

    View Slide

  72. HOMEWORK
    Read on principles of
    component coupling and
    cohesion.

    View Slide

  73. THANK YOU!

    View Slide