Pro Yearly is on sale from $80 to $50! »

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.

1a4e1f98f3aeef310273366c8c785207?s=128

Jakub Zalas

April 07, 2014
Tweet

Transcript

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

  2. @jakzal @jakub_zalas @SensioLabsUK £  

  3. COUPLING

  4. "MODULES ARE COUPLED IF CHANGING ONE OF THEM REQUIRES CHANGING

    ANOTHER ONE." http://martinfowler.com/ieeeSoftware/coupling.pdf Martin Fowler
  5. tight coupling loose coupling

  6. COUPLING AND COHESION

  7. tight coupling low cohesion

  8. loose coupling high cohesion

  9. WHY IS TIGHT COUPLING BAD?

  10. WHY IS LOOSE COUPLING GOOD?

  11. REUSABILITY

  12. READABILITY

  13. MAINTENANCE

  14. CHANGE

  15. LOOSEN UP? How to

  16. MAKE DEPENDENCIES EXPLICIT

  17. class PackageCrawler { public function crawl($resource) { $browser = Browser::getInstance();

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

    = new Browser(); $response = $browser->get($resource); // @todo parse } }
  19. DEPENDENCY INJECTION !

  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 } }
  21. INJECT EXACTLY WHAT YOU NEED

  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 } }
  23. https://twitter.com/iamtankist/status/359382451816112129

  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 } }
  25. DON'T MIX LAYERS

  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 } }
  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 } }
  28. BI-DIRECTIONAL DEPENDENCIES ARE A SMELL

  29. BREAK DEPENDENCIES

  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 } }
  31. None
  32. None
  33. interface HttpClient { public function get($resource, array $headers = []);

    }
  34. class PackageCrawler { private $httpClient; public function __construct(HttpClient $httpClient) {

    $this->httpClient = $httpClient; } public function crawl($resource) { $response = $this->httpClient->get($resource); // @todo parse } }
  35. None
  36. None
  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 ); } }
  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(); } }
  39. DEFER DECISIONS

  40. DEFER COMMITMENT

  41. FRAMEWORK?

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

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

  45. CLEAN ARCHITECTURE

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

  47. SOURCE CODE DEPENDENCIES CAN ONLY POINT INWARDS

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

  49. Application Doctrine In memory UI Tests CLI

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

    a database […]" http://alistair.cockburn.us/Hexagonal+architecture
  51. UI IS AN IMPLEMENTATION DETAIL

  52. DATABASE IS AN IMPLEMENTATION DETAIL

  53. FRAMEWORK IS AN IMPLEMENTATION DETAIL

  54. None
  55. STEP 0 MAKE DEPENDENCIES EXPLICIT

  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; } }
  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) ); } }
  58. STEP 1 ONLY INJECT WHAT YOU ACTUALLY NEED

  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; } }
  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; } }
  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) ); } }
  62. STEP 2 DON'T TALK TO THE INFRASTRUCTURE DIRECTLY

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

    * * @return Product[] */ public function search($keywords); }
  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; } }
  65. None
  66. DON'T FOLLOW THE FRAMEWORK

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

  68. MAKE IT OPTIONAL

  69. A GOOD FRAMEWORK WILL HELP YOU

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

  71. DON'T START WITH THE FRAMEWORK

  72. MAKE YOUR APPLICATION INDEPENDENT

  73. AND THEN INTEGRATE IT WITH THE FRAMEWORK

  74. REMEMBER!

  75. TDD IS YOUR FRIEND

  76. THANK YOU!

  77. Credits