The Dependency Trap

1a4e1f98f3aeef310273366c8c785207?s=47 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.

1a4e1f98f3aeef310273366c8c785207?s=128

Jakub Zalas

September 26, 2014
Tweet

Transcript

  1. The Dependency Trap Jakub Zalas

  2. @jakzal @jakub_zalas @SensioLabsUK £  

  3. LET'S WRITE A CRAWLER A random story

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

    } }
  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 } }
  6. None
  7. None
  8. WE NEED TO REPLACE THAT LIBRARY What if I told

    you…
  9. It's a trap!

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

    them requires changing another one."
  11. tight coupling loose coupling

  12. WHEN COUPLING INCREASES?

  13. A has an attribute that refers to B use Buzz\Browser;

    class Crawler { /** * @var Browser */ private $browser; }
  14. A calls methods on B class Crawler { private $c;

    public function crawl($url) { $this->c->getBrowser()->get($url); } }
  15. A has a method that references B use Buzz\Browser; class

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

    Crawler { /** * @return Browser */ public function crawl($url) {} }
  17. A extends or implements B use Buzz\Browser; class Crawler extends

    Browser { public function crawl($url) { $this->get($url); } }
  18. LET'S JUST DO IT OURSELVES If that's so bad

  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 } }
  20. Cohesion describes how closely are elements in a module related

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

    TO CHANGE
  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
  23. tight coupling low cohesion

  24. loose coupling high cohesion

  25. LOOSE COUPLING! Give us

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

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

    modules. Both should depend on abstractions. An abstraction!
  28. The Dependency-Inversion Principle Both should depend on abstractions.

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

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

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

  32. class PackageCrawler { private $contentProvider; public function __construct(ContentProvider $provider) {

    $this->contentProvider = $provider; } public function crawl($resource) { $response = $this->contentProvider->fetch($resource); // @todo parse } }
  33. None
  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 ); } }
  35. None
  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(); } }
  37. None
  38. None
  39. WHY IS TIGHT COUPLING BAD?

  40. WHY IS LOOSE COUPLING GOOD?

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

    • easier to maintain • easier to change
  42. ENABLE CHANGE!

  43. DEFER DECISIONS

  44. DEFER COMMITMENT

  45. FRAMEWORK?

  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) ); } }
  47. LAYERED ARCHITECTURE

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

  49. CLEAN ARCHITECTURE

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

  51. SOURCE CODE DEPENDENCIES CAN ONLY POINT INWARDS

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

  53. Application Doctrine In memory UI Tests CLI

  54. "Create your application to work without either a UI or

    a database […]" http://alistair.cockburn.us/Hexagonal+architecture
  55. LET'S FIX IT just a little bit

  56. LET'S MAKE DEPENDENCIES EXPLICIT

  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; } }
  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) ); } }
  59. LET'S INJECT WHAT WE ACTUALLY NEED

  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; } }
  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; } }
  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) ); } }
  63. LET'S NOT TALK TO THE INFRASTRUCTURE DIRECTLY

  64. namespace Acme\ProductCatalog; interface ProductRepository { /** * @param string $keywords

    * * @return Product[] */ public function search($keywords); }
  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; } }
  66. None
  67. CHANGES ARE THE ONLY CONSTANT

  68. WE CANNOT PREDICT EVERY CHANGE

  69. WE CANNOT PREPARE FOR EVERY CHANGE

  70. PREMATURE ABSTRACTION IS EVIL

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

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

  73. THANK YOU!