Slide 1

Slide 1 text

WHEN EASY IS HARD 15 June 2015

Slide 2

Slide 2 text

Marek Matulka Software Engineer at SensioLabsUK @super_marek

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

http://london2015.live.symfony.com/

Slide 5

Slide 5 text

Take it easy!

Slide 6

Slide 6 text

Framework

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

Convenience

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Cost

Slide 14

Slide 14 text

price speed quality

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

Let’s grow!

Slide 17

Slide 17 text

New features

Slide 18

Slide 18 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 19

Slide 19 text

No content

Slide 20

Slide 20 text

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

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

What has happened?

Slide 30

Slide 30 text

What has happened? - too close to the framework

Slide 31

Slide 31 text

What has happened? - too close to the framework - no separation of concerns

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

delivered features time Features delivered over time

Slide 35

Slide 35 text

How to avoid?

Slide 36

Slide 36 text

Forget the framework!

Slide 37

Slide 37 text

Decouple your code

Slide 38

Slide 38 text

Decoupled code - improves readability

Slide 39

Slide 39 text

Decoupled code - improves readability - can be reused

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

Let’s do it!

Slide 43

Slide 43 text

Global scope is a smell! Avoid hidden dependencies

Slide 44

Slide 44 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 45

Slide 45 text

Inject only what you need

Slide 46

Slide 46 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 47

Slide 47 text

Don’t mix layers

Slide 48

Slide 48 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 49

Slide 49 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 50

Slide 50 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 51

Slide 51 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 52

Slide 52 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 53

Slide 53 text

Invert dependencies

Slide 54

Slide 54 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 55

Slide 55 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 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

EnrolmentRepository DoctrineEnrolmentRepository InMemoryEnrolmentRepositor y EntityManager Dependency Inversion InMemoryRepository

Slide 59

Slide 59 text

Clean Code

Slide 60

Slide 60 text

Layered Architecture

Slide 61

Slide 61 text

User Interface Application Domain Infrastructure

Slide 62

Slide 62 text

Clean Architecture by Robert C. Martin

Slide 63

Slide 63 text

Entities Use Cases Controllers UI

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

aka Ports & Adapters Hexagonal Architecture

Slide 66

Slide 66 text

UI Adapter Log Adapter Data Storage Adapter Domain External Data Adapter

Slide 67

Slide 67 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 68

Slide 68 text

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

Slide 69

Slide 69 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 70

Slide 70 text

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

Slide 71

Slide 71 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 72

Slide 72 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 73

Slide 73 text

(five more classes to write!) Hard!

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

Why bother?

Slide 76

Slide 76 text

Your Domain First

Slide 77

Slide 77 text

Domain models

Slide 78

Slide 78 text

Domain models Use Cases

Slide 79

Slide 79 text

Domain models Use Cases Controllers

Slide 80

Slide 80 text

Domain models Use Cases Commands Infrastructure

Slide 81

Slide 81 text

Clean Code!

Slide 82

Slide 82 text

Clean code - decoupled - reusable - testable - maintainable - changeable

Slide 83

Slide 83 text

Sustainable!

Slide 84

Slide 84 text

Easy! Adding new features?

Slide 85

Slide 85 text

delivered features time Features delivered over time

Slide 86

Slide 86 text

Recap

Slide 87

Slide 87 text

It’s your choice

Slide 88

Slide 88 text

and make the changes hard Start the easy way

Slide 89

Slide 89 text

and make the changes easy Start the hard way

Slide 90

Slide 90 text

“Everything is hard before it is easy” - Goethe

Slide 91

Slide 91 text

Questions?

Slide 92

Slide 92 text

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