Slide 1

Slide 1 text

The Dependency Trap Jakub Zalas

Slide 2

Slide 2 text

@jakzal @jakub_zalas @SensioLabsUK £  

Slide 3

Slide 3 text

LET'S WRITE A CRAWLER A random story

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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 } }

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

It's a trap!

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

tight coupling loose coupling

Slide 12

Slide 12 text

WHEN COUPLING INCREASES?

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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 } }

Slide 20

Slide 20 text

Cohesion describes how closely are elements in a module related

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

tight coupling low cohesion

Slide 24

Slide 24 text

loose coupling high cohesion

Slide 25

Slide 25 text

LOOSE COUPLING! Give us

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

The Dependency-Inversion Principle Both should depend on abstractions.

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

The Dependency-Inversion Principle Details should depend upon abstractions.

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

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 ); } }

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

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(); } }

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

WHY IS TIGHT COUPLING BAD?

Slide 40

Slide 40 text

WHY IS LOOSE COUPLING GOOD?

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

ENABLE CHANGE!

Slide 43

Slide 43 text

DEFER DECISIONS

Slide 44

Slide 44 text

DEFER COMMITMENT

Slide 45

Slide 45 text

FRAMEWORK?

Slide 46

Slide 46 text

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) ); } }

Slide 47

Slide 47 text

LAYERED ARCHITECTURE

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

CLEAN ARCHITECTURE

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

SOURCE CODE DEPENDENCIES CAN ONLY POINT INWARDS

Slide 52

Slide 52 text

HEXAGONAL ARCHITECTURE a.k.a. PORTS AND ADAPTERS

Slide 53

Slide 53 text

Application Doctrine In memory UI Tests CLI

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

LET'S FIX IT just a little bit

Slide 56

Slide 56 text

LET'S MAKE DEPENDENCIES EXPLICIT

Slide 57

Slide 57 text

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; } }

Slide 58

Slide 58 text

// .. 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) ); } }

Slide 59

Slide 59 text

LET'S INJECT WHAT WE ACTUALLY NEED

Slide 60

Slide 60 text

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; } }

Slide 61

Slide 61 text

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; } }

Slide 62

Slide 62 text

// .. 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) ); } }

Slide 63

Slide 63 text

LET'S NOT TALK TO THE INFRASTRUCTURE DIRECTLY

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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; } }

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

CHANGES ARE THE ONLY CONSTANT

Slide 68

Slide 68 text

WE CANNOT PREDICT EVERY CHANGE

Slide 69

Slide 69 text

WE CANNOT PREPARE FOR EVERY CHANGE

Slide 70

Slide 70 text

PREMATURE ABSTRACTION IS EVIL

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

HOMEWORK Read on principles of component coupling and cohesion.

Slide 73

Slide 73 text

THANK YOU!