Hexagonal symfony

Post on 08-Sep-2014

4,751 views 3 download

Tags:

description

What would your application look like if it were written by the people who write the testing frameworks? If unit tests make classes more modular, by forcing you to test it in isolation, then what is the effect of expanding this to a less granular level, the acceptance and functional test. The more modern application architecture evolves, the more we hear the very old patterns being rediscovered and re-adopted. 1979 Trygve's MVC is a classic example, so are the SOLID principles. In this talk we will look on how Symfony allows for a really decoupled, easy to test application, by following on the footsteps of Alistair Cockburn's hexagonal architecture.

transcript

Hexagonal SymfonyMarcello Duarte

@_md SensioLabs

@_md SensioLabs

About me

Marcello DuarteSensioLabs

UK

@_md

work

contribute

tweet

@_md SensioLabs

Why do we care about maintainability?

@_md SensioLabs

Convenience vs Maintainability

@_md SensioLabs

Maintainability is not a thing for the future

@_md SensioLabs

“We cannot manage what we cannot measure”

@_md SensioLabs

“We cannot manage what we cannot measure”

@_md SensioLabs

We cannot manage what we cannot

change

@_md SensioLabs

“A framework is ‘just’ one of the tools to help you develop better and faster” – Symfony documentation

@_md SensioLabs

Faster is not enough

@_md SensioLabs

Growable http

://up

load

.wik

imed

ia.o

rg/w

ikip

edia

/com

mon

s/3/

39/D

omes

tic_G

oose

.jpg

@_md SensioLabs

“Key in making great and growable systems is to

design how its modules communicate

[and not] what their properties and behaviours should be.”

View

poin

ts R

esea

rch

Inst

itute

Sou

rce

- Bon

nie

Mac

bird

UR

L -h

ttp://

ww

w.vp

ri.or

g

@_md SensioLabs

real procedure Sum (k, l, u, ak) value l, u; integer k, l, u; real ak; begin real S; S := 0; for k := l step 1 until u do S := S + ak; Sum := S; end; x := Sum( i, 1, n, V[i] );

@_md SensioLabs

From Block Structure to OO

@_md SensioLabs

Glyph Class Char (c); Character c; Begin Procedure print; OutChar(c); End;

@_md SensioLabs

Module A Module B

flow of control? source code dependency?

@_md SensioLabs

controller

use case

utility

uses

uses

Naïve Implementation

@_md SensioLabs

!public function updateAction($id) { $em = $this->getDoctrine()->getManager(); $product = $em->getRepository('AcmeStoreBundle:Product')->find($id); ! if (!$product) { throw $this->createNotFoundException( 'No product found for id '.$id ); } ! $product->setName('New product name!'); $em->flush(); ! return $this->redirect($this->generateUrl('homepage')); }

@_md SensioLabs

!public{ $em $product ! ! $product $em! }

Doctrine

RedirectModel

Router

@_md SensioLabs

Module A Polymorphic

Dependency Inversion Principle

Module B

[Martin 02]

@_md SensioLabs

use case

service adapter

utility adapter

service

utility

@_md SensioLabs

Layered Architecture

User Interface

Application

Domain

Infrastructure

[Evans 04]

@_md SensioLabs

@_md SensioLabs

“The hexagon is not a hexagon because the number six is important, but rather to allow the people doing the drawing

to have room to insert ports and adapters as they need”

:)

[Cockburn 08]

@_md SensioLabshttp://www.flickr.com/photos/10849858@N00/2696404813

Ports are the interfaces that describe the relationship of your application with the outside world

@_md SensioLabshttp://www.flickr.com/photos/mac_users_guide/3680586328/

@_md SensioLabs

Hexagonal Architecture

your application

@_md SensioLabs

web gui app

your application

user side ports

test adapter

@_md SensioLabs

web gui app

your application

user side ports

test adapter

data side ports

db access

in memory

db

@_md SensioLabs

Dbrest

Logs

@_md SensioLabs

“Architecture is about intent”

[Martin 02]

START WRITING A UNIT TEST FOR THE ENTRY POINT,

BUT INSTEAD OF IMMEDIATELY TRYING TO SOLVE THE PROBLEM, INTENTIONALLY

DEFER WRITING ANY IMPLEMENTATION LOGIC! INSTEAD, BREAK DOWN THE

PROBLEM BY DREAMING UP ALL OF THE OBJECTS YOU WISH YOU HAD

Just

in S

earl

s

KNOW IN ADVANCE WHERE YOU ARE GOING TO PUT CERTAIN

BEHAVIOURS. […] IN SHORT, BEFORE YOU WRITE YOUR FIRST TEST,

YOU HAVE TO "DREAM UP THE [BOUNDARIES] THAT YOU WISH YOU

HAD".U

ncle

Beb

@_md SensioLabs

Dream the feature

@_md SensioLabs

@_md SensioLabs

Dream the boundaries

@_md SensioLabs

L&D app !

!

Learning Domain

Google Auth Employees

API

Twitter bootstrap

@_md SensioLabs

Express intent

@_md SensioLabs

<—— Domain<—— Application<—— Delivery Port<—— Persistency Port

@_md SensioLabs

Context also have boundaries

@_md SensioLabs

@_md SensioLabs

Controllers depend on use cases

@_md SensioLabs

/** * @Route(service="controllers.skills") */ class SkillsController { private $competencyFinder; private $raterFinder; ! public function __construct(CompetencyFinder $competencyFinder) { $this->competencyFinder = $competencyFinder; } ! /** * @Route("/skills", name="skills") * @Template() * Show the skill for the learners role */ public function indexAction() { return ['competencies' => $this->competencyFinder->findDictionary()]; } }

@_md SensioLabs

Use cases depend on the domain services and ports

@_md SensioLabs

<?php !namespace Inviqa\TeamUp\Skills; !use Inviqa\Learning\Learner; use Inviqa\Learning\Skills\CompetencyDictionaryFinder; !class CompetencyFinder { private $learner; ! private $finder; ! public function __construct(Learner $learner, CompetencyDictionaryFinder $finder) { $this->learner = $learner; $this->finder = $finder; } ! public function findDictionary() { return $this->finder->findByLearnerRole($this->learner->getLearnerRole()); } }

@_md SensioLabs

<?php !namespace Inviqa\Learning\Skills; !use Inviqa\Learning\LearnerRole; !interface CompetencyDictionaryFinder { /** * Gets the dictionary for a particular role * * @param LearnerRole $role * @return \Inviqa\Learning\CompetencyDictionary */ public function findByLearnerRole(LearnerRole $role); }

@_md SensioLabs

Leverage Symfony Event Dispatcher Keep Controller stuff in the controller

@_md SensioLabs

public function onSuccess(Event $event) { $this->flashBag->add('success', 'Project has been created.'); } ! public function onFailure(Event $event) { $this->flashBag->add( 'failure', 'Failed to create project.' . PHP_EOL . $event->get(‘reason') ); }

@_md SensioLabs

And make you controller a listener of your use case

@_md SensioLabs

github.com/MarcelloDuarte/hexagonal-symfony

@_md SensioLabs

Thanks!

@_md SensioLabs

Questions?

joind.in/10781 github.com/MarcelloDuarte/hexagonal-symfony