hexagonal symfony
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