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. 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 } }
  2. A has an attribute that refers to B use Buzz\Browser;

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

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

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

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

    Browser { public function crawl($url) { $this->get($url); } }
  7. 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 } }
  8. 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
  9. The Dependency-Inversion Principle High-level modules should not depend on low-level

    modules. Both should depend on abstractions. An abstraction!
  10. class PackageCrawler { private $contentProvider; public function __construct(ContentProvider $provider) {

    $this->contentProvider = $provider; } public function crawl($resource) { $response = $this->contentProvider->fetch($resource); // @todo parse } }
  11. 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 ); } }
  12. 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(); } }
  13. Decoupled code is • easier to read (understand) • easier to reuse

    • easier to maintain • easier to change
  14. 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) ); } }
  15. "Create your application to work without either a UI or

    a database […]" http://alistair.cockburn.us/Hexagonal+architecture
  16. 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; } }
  17. // .. 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) ); } }
  18. 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; } }
  19. 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; } }
  20. // .. 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) ); } }
  21. namespace Acme\ProductCatalog; interface ProductRepository { /** * @param string $keywords

    * * @return Product[] */ public function search($keywords); }
  22. 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; } }