Slide 1

Slide 1 text

WHEN EASY IS HARD 12 Mar 2015

Slide 2

Slide 2 text

Marek Matulka Software Engineer at SensioLabsUK @super_marek

Slide 3

Slide 3 text

#Symfony_Live @SensioLabsUK

Slide 4

Slide 4 text

Take it easy!

Slide 5

Slide 5 text

Framework

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Convenience

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

© http://www.yiiframework.com/tour/

Slide 10

Slide 10 text

User::findOrFail($id)]); } }

Slide 11

Slide 11 text

getDoctrine() ->getRepository('AcmeConferenceBundle:Conference') ->find($id); $this->get('acme_conference.enrolment.service') ->enrol($conference, $this->getUser())) { return new Response('Success!'); } }

Slide 12

Slide 12 text

Cost

Slide 13

Slide 13 text

price speed quality

Slide 14

Slide 14 text

© http://the-language-corner.com/noticias/wp-content/uploads/2015/01/academia-ingl%C3%A9s-en-Madrid.jpg Phase 1 completed!

Slide 15

Slide 15 text

Let’s grow!

Slide 16

Slide 16 text

New features

Slide 17

Slide 17 text

getDoctrine() ->getRepository('AcmeConferenceBundle:Conference')->find($id); if ( $enrolment = $this->get('acme_conference.enrolment.service') ->enrol($conference, $this->getUser()) ) { $em = $this->getDoctrine()->getEntityManager(); $em->persist($enrolment); $em->flush(); $this->get('acme_conference.notification.service') ->notify($this->getUser(), 'success'); return $this->redirect('registration_successful') } return $this->redirect('registration_failed'); } } © http://warrenmars.com/

Slide 18

Slide 18 text

Difficult to maintain

Slide 19

Slide 19 text

© http://www.smosh.com/smosh-pit/memes/20-best-i-didnt-choose-thug-life-memes

Slide 20

Slide 20 text

It takes longer to add new features

Slide 21

Slide 21 text

Yummy spaghetti code! © http://momeefriendsli.com/2013/04/16/dinner-and-a-book/

Slide 22

Slide 22 text

…and there are no tests!

Slide 23

Slide 23 text

No one wants to touch it

Slide 24

Slide 24 text

Let’s rewrite it!

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

not so happy customer © https://channelinstincts.files.wordpress.com/2014/01/unhappy-customer.jpg

Slide 27

Slide 27 text

price speed quality

Slide 28

Slide 28 text

price speed sustainability

Slide 29

Slide 29 text

Do you expect the plumber to “apply the workaround” to your leaking pipe? © http://www.asbestosjustice.co.uk/wp-content/uploads/2014/11/jobs-to-consider-7-plumber-1093362-TwoByOne.jpg

Slide 30

Slide 30 text

What has happened?

Slide 31

Slide 31 text

What has happened? - too close to the framework - no separation of concerns - mixed layers - no tests!

Slide 32

Slide 32 text

delivered features time Features delivered over time

Slide 33

Slide 33 text

How to avoid?

Slide 34

Slide 34 text

Forget the framework!

Slide 35

Slide 35 text

Decouple your code

Slide 36

Slide 36 text

Decoupled code - improves readability - can be reused - easy to maintain - easy to change

Slide 37

Slide 37 text

Let’s do it!

Slide 38

Slide 38 text

Global scope is a smell! Avoid hidden dependencies

Slide 39

Slide 39 text

class RegistrationController { public function registerAction($id) { $learner = Session::getUser(); $conference = Conference::findOrFail($id); Enrolment::enrolLearnerInConference($learner, $conference); Mail::send('emails.welcome', array('key' => 'value'), function($message) use ($learner, $conference) { $message->to($learner->email(), $learner->name()) ->subject('Thanks for registering for '.$conference->name().'!'); } ); } }

Slide 40

Slide 40 text

Inject only what you need

Slide 41

Slide 41 text

class RegistrationController { public function __construct( Enrolment $enrolment, Learner $learner, Notifier $notifier ) { $this->enrolment = $enrolment; $this->learner = $learner; $this->notifier = $notifier; } public function registerAction(Conference $conference) { $enrolment = $this->enrolment->enrolLearnerInConference($this->learner, $conference); $this->notifier->successfulEnrolment($enrolment); } }

Slide 42

Slide 42 text

Don’t mix layers

Slide 43

Slide 43 text

class RegistrationController { public function __construct( EnrolmentHandler $enrolmentHandler, Learner $learner ) { $this->enrolmentHandler = $enrolmentHandler; $this->learner = $learner; } public function registerAction(Conference $conference) { $this->enrolmentHandler->handle( new EnrolLearnerInConference($this->learner, $conference); ); } }

Slide 44

Slide 44 text

class RegistrationController { public function __construct( EnrolmentHandler $enrolmentHandler, Learner $learner ) { $this->enrolmentHandler = $enrolmentHandler; $this->learner = $learner; } public function registerAction(Conference $conference) { $this->enrolmentHandler->handle( new EnrolLearnerInConference($this->learner, $conference); ); } }

Slide 45

Slide 45 text

class RegistrationController { public function registerAction($id) { $learner = Session::getUser(); $conference = Conference::findOrFail($id); Enrolment::enrolLearnerInConference($learner, $conference); Mail::send('emails.welcome', array('key' => 'value'), function($message) use ($learner, $conference) { $message->to($learner->email(), $learner->name()) ->subject('Thanks for registering for '.$conference->name().'!'); } ); } }

Slide 46

Slide 46 text

class EnrolmentHandler { public function __construct(EnrolmentRepository $enrolmentRepository, Mailer $mailer) { $this->repository = $enrolmentRepository; $this->mailer = $mailer; } public function handle(EnrolLearnerInConference $enrolmentCommand) { $enrolment = $enrolmentCommand->execute(); $this->repository>save($enrolment); $this->mailer->send('emails.welcome', array('key' => 'value'), function($message) use ($enrolment) { $message->to($enrolment->learner()->email(), $enrolment->learner()->name()) ->subject('Thanks for registering for '.$enrolment->conference->name().'!'); }); }

Slide 47

Slide 47 text

Invert dependencies

Slide 48

Slide 48 text

class EnrolmentHandler { public function __construct(EnrolmentRepository $enrolmentRepository, Mailer $mailer) { $this->repository = $enrolmentRepository; $this->mailer = $mailer; } public function handle(EnrolLearnerInConference $enrolmentCommand) { $enrolment = $enrolmentCommand->execute(); $this->repository>save($enrolment); $this->mailer->send('emails.welcome', array('key' => 'value'), function($message) use ($enrolment) { $message->to($enrolment->learner()->email(), $enrolment->learner()->name()) ->subject('Thanks for registering for '.$enrolment->conference->name().'!'); }); }

Slide 49

Slide 49 text

class EnrolmentHandler { public function __construct( EnrolmentRepository $enrolmentRepository, EnrolmentNotifier $notifier ) { $this->repository = $enrolmentRepository; $this->notifier = $notifier; } public function handle(EnrolLearnerInConference $enrolmentCommand) { if ($enrolment = $enrolmentCommand->execute()) { $this->repository->save($enrolment); $this->notifier->successfulEnrolment($enrolment); } } }

Slide 50

Slide 50 text

interface EnrolmentNotifier { /** * @param Enrolment $enrolment * * @throws FaildToNotify */ public function successfulEnrolment(Enrolment $enrolment); }

Slide 51

Slide 51 text

interface EnrolmentRepository { /** * @param integer $id * @return Enrolment */ public function findOneById($id); /** * @param Enrolment $enrolment */ public function save(Enrolment $enrolment); }

Slide 52

Slide 52 text

EnrolmentRepository DoctrineEnrolmentRepository InMemoryEnrolmentRepositor y EntityManager Dependency Inversion InMemoryRepository

Slide 53

Slide 53 text

Clean Code

Slide 54

Slide 54 text

Layered Architecture

Slide 55

Slide 55 text

User Interface Application Domain Infrastructure

Slide 56

Slide 56 text

Clean Architecture by Robert C. Martin

Slide 57

Slide 57 text

Entities Use Cases Controllers Web

Slide 58

Slide 58 text

Entities Use Cases Controllers Web Framework Drivers Interface Adapters Application Business Rules Enterprise Business Rules

Slide 59

Slide 59 text

aka Ports & Adapters Hexagonal Architecture

Slide 60

Slide 60 text

UI Adapter Log Adapter Data Storage Adapter Domain External Data Adapter

Slide 61

Slide 61 text

UI Adapter Log Adapter Data Storage Adapter Domain External Data Adapter UI client port log adapter port persistence layer port data provider port

Slide 62

Slide 62 text

UI Adapter Monolog Test Adapter NullLogger Doctrine InMemory Domain Facebook API Mock API

Slide 63

Slide 63 text

Why bother?

Slide 64

Slide 64 text

Why bother writing clean code? - decoupled - reusable - testable - maintainable - changeable

Slide 65

Slide 65 text

Sustainable!

Slide 66

Slide 66 text

class EnrolmentHandler { public function __construct( EnrolmentRepository $enrolmentRepository, EnrolmentNotifier $notifier ) { $this->repository = $enrolmentRepository; $this->notifier = $notifier; } public function handle(EnrolLearnerInConference $enrolmentCommand) { if ($enrolment = $enrolmentCommand->execute()) { $this->repository->save($enrolment); $this->notifier->successfulEnrolment($enrolment); } } }

Slide 67

Slide 67 text

class DoctrineEnrolmentRepository implements EnrolmentRepository { public function __construct(EntityManager $entityManager) { $this->em = $entityManager; } public function findOneById($id) { return $this->em->find(Enrolment::class, $id); } public function save(Enrolment $enrolment) { $this->em->persist($enrolment); $this->em->flush($enrolment); } }

Slide 68

Slide 68 text

class InMemoryEnrolmentRepository implements EnrolmentRepository { public function __construct() { $this->repository = new InMemoryRepository(); } public function findOneById($id) { return $this->repository->findById($id); } public function save(Enrolment $enrolment) { $this->repository->save($enrolment); } }

Slide 69

Slide 69 text

(five more classes to write!) Hard!

Slide 70

Slide 70 text

delivered features time Features delivered over time

Slide 71

Slide 71 text

Recap

Slide 72

Slide 72 text

It’s your choice

Slide 73

Slide 73 text

and make the changes hard Start the easy way

Slide 74

Slide 74 text

and make the changes easy Start the hard way

Slide 75

Slide 75 text

“Everything is hard before it is easy” - Goethe

Slide 76

Slide 76 text

Questions?

Slide 77

Slide 77 text

Thank you! https://speakerdeck.com/super_marek @super_marek