Slide 1

Slide 1 text

Microservices within a Monolith Join at slido.com #devdays2019 @jlammerts

Slide 2

Slide 2 text

Joop Lammerts Developer @procurios for +3 years @jlammerts

Slide 3

Slide 3 text

Procurios Cluster

Slide 4

Slide 4 text

Procurios Cluster for context

Slide 5

Slide 5 text

Our Monolith

Slide 6

Slide 6 text

Our monolith ~ backend: 3.000.000 lines of code distributed over 18.000 PHP files ~ frontend: 350.000 lines in 1800 JavaScript files 800.000 lines of CSS code

Slide 7

Slide 7 text

Usage

Slide 8

Slide 8 text

Usage ● 2000 clients ● 800.000 users ● 500.000 visitors an hour

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

Monolith

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

Microservices

Slide 13

Slide 13 text

Service #1 Service #2 Service #3

Slide 14

Slide 14 text

Microservices, or microservice architecture, is an approach to application development in which a large application is built as a suite of modular components or services. Assumption

Slide 15

Slide 15 text

Microservices, or microservice architecture, is an approach to application development in which a large application is built as a suite of modular components or services. Assumption

Slide 16

Slide 16 text

Modules

Slide 17

Slide 17 text

Modular Monolith

Slide 18

Slide 18 text

Modular Monolith ● Bounded context with no dependencies on each other ● Information can be duplicated for each bounded context

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

What to do with your legacy Monolith?

Slide 21

Slide 21 text

Project |--- core |--- modules |--- cms |--- meeting |--- relation |--- user

Slide 22

Slide 22 text

Project |--- core |--- modules |--- cms |--- meeting -> attendee -> meeting -> registration -> ticket |--- relation |--- user

Slide 23

Slide 23 text

final class RegistrationController { public function register() { $userId = $_SESSION['userId']; $pdo = new \PDO('localhost'); $statement = $pdo->prepare(" SELECT * FROM `user` WHERE `id` = ? "); $statement->execute([$userId]); $userData = $statement->fetch()[0]; if (!$userData) { HttpResponse::redirect('/login'); } $form = $this->getRegistrationForm(); $data = $form->getData(); if (!$data['meetingId'] || !$data['ticketId'] || !$data['remark']) { return $form; } $statement = $pdo->prepare(" UPDATE `tickets` SET `sold` = 1 WHERE WHERE `id` = ? "); $statement->execute([ $data['ticketId'] ]); if ($statement->rowCount() !== 1) { return 'There are no tickets available'; } $statement = $pdo->prepare(" INSERT INTO `attendee` SET `user_id` = ?, `first_name` = ?, `last_name` = ?, `meeting_id` = ?, `ticket_id` = ?, `remark` = ?, "); $statement->execute([ $userData['id'], $userData['firstName'], $userData['name'], $data['meetingId'], $data['ticketId'], $data['remark'], ]); /* * send confirmation stuff */ \HttpResponse::redirect('/'); } }

Slide 24

Slide 24 text

namespace Meeting\Registration; final class RegistrationController { public function register() { $userId = $_SESSION['userId']; $pdo = new \PDO('localhost'); $statement = $pdo->prepare(" SELECT * FROM `user` WHERE `id` = ? "); $statement->execute([$userId]); $userData = $statement->fetch()[0]; if (!$userData) { HttpResponse::redirect('/login'); } // }

Slide 25

Slide 25 text

namespace Meeting\Registration; final class RegistrationController { public function register() { $user = UserService::getCurrentUser(); if (!$user->isAuthenticated()) { HttpResponse::redirect('/login'); } // }

Slide 26

Slide 26 text

namespace User; final class UserService { public static function getCurrentUser(): User { $userId = $_SESSION['userId'] ?? null; if ($userId === null) { return User::guest(); } $pdo = new \PDO('localhost'); $statement = $pdo->prepare(" SELECT * FROM `user` WHERE `id` = ? "); $statement->execute([$userId]); $userData = $statement->fetch()[0]; return $userData ? User::populate($userData); : User::guest(); }

Slide 27

Slide 27 text

User is now a bounded context

Slide 28

Slide 28 text

final class RegistrationController { public function register() { $user = UserService::getCurrentUser(); if (!$user->isAuthenticated()) { HttpResponse::redirect('/login'); } $form = $this->getRegistrationForm(); $data = $form->getData(); if (!$data['meetingId'] || !$data['ticketId'] || !$data['remark']) { return $form; } $statement = $pdo->prepare(" UPDATE `tickets` SET `sold` = 1 WHERE WHERE `id` = ? "); $statement->execute([ $data['ticketId'] ]); if ($statement->rowCount() !== 1) { return 'There are no tickets available'; } $statement = $pdo->prepare(" INSERT INTO `attendee` SET `user_id` = ?, `first_name` = ?, `last_name` = ?, `meeting_id` = ?, `ticket_id` = ?, `remark` = ?, "); $statement->execute([ $userData['id'], $userData['firstName'], $userData['name'], $data['meetingId'], $data['ticketId'], $data['remark'], ]); /* * send confirmation stuff */ \HttpResponse::redirect('/'); } }

Slide 29

Slide 29 text

But wait, there is more!

Slide 30

Slide 30 text

public function register() { // $form = $this->getRegistrationForm(); $data = $form->getData(); if (!$data['meetingId'] || !$data['ticketId'] || !$data['remark']) { return $form; } $statement = $pdo->prepare(" /* */ "); $statement->execute([ $data['ticketId'] ]); if ($statement->rowCount() !== 1) { return 'There are no tickets available'; } // }

Slide 31

Slide 31 text

public function register() { // $form = $this->getRegistrationForm(); $data = $form->getData(); if (!$data['meetingId'] || !$data['ticketId'] || !$data['remark']) { return $form; } try { $ticket = TicketService::purchase($data['ticketId']); } catch (CouldNotPurchaseTicket $e) { return 'There are no tickets available'; } // }

Slide 32

Slide 32 text

final class TicketService { public static function purchase(int $ticketId): Ticket { $connection = DB::getConnection(); $statement = $connection->prepare(" /* */ "); $statement->execute([ $data['ticketId'] ]); if ($statement->rowCount() !== 1) { throw CouldNotPurchaseTicket::becauseNoTicketsLef(); } return new Ticket($ticketId); } }

Slide 33

Slide 33 text

public function register() { // $statement = $pdo->prepare(" /* */ "); $statement->execute([ $user->getId(), $user->getFirstName(), $user->getName(), $data['meetingId'], $data['ticketId'], $data['remark'], ]); // }

Slide 34

Slide 34 text

public function register() { // $attendee = new Attendee($user->getId(), $user->getFirstName(), $user->getName()); $registration = new Registration($data['meetingId'], $attendee, $ticket, $data['remark']); RegistrationService::register($registration); /* * send confirmation stuff */ HttpResponse::redirect('/'); }

Slide 35

Slide 35 text

Modules sent messages

Slide 36

Slide 36 text

final class RegistrationService { public static function register(Regitstation $registration): void { $repository = self::getRepository(); $repository->save($registration); $registrationCreated = new RegistrationCreated($registration); foreach (self::getListenerProvider()->getListenersForEvent($registrationCreated) as $listener) { $listener->handle($registrationCreated); } } }

Slide 37

Slide 37 text

final class NotifyAttendee { public function handle(RegistrationCreated $registrationCreated): void { $registration = $registrationCreated->getRegistration(); mail( $registration->getEmailAddressAsString(), ‘See you soon’, sprintf( ‘Hi %s
See you soon at %s ‘, $registration->getFirstName(), $registration->getMeetingName() ); } }

Slide 38

Slide 38 text

final class RegistrationController { public function register() { $user = UserService::getCurrentUser(); if (!$user->isAuthenticated()) { HttpResponse::redirect('/login'); } $form = $this->getRegistrationForm(); $data = $form->getData(); if (!$data['meetingId'] || !$data['ticketId'] || !$data['remark']) { return $form; } try { $ticket = TicketService::purchase($data['ticketId']); } catch (CouldNotPurchaseTicket $e) { return 'There are no tickets available'; } $attendee = new Attendee($user->getId(), $user->getFirstName(), $user->getName()); $registration = new Registration($data['meetingId'], $attendee, $ticket, $data['remark']); RegistrationService::register($registration); HttpResponse::redirect('/'); } }

Slide 39

Slide 39 text

But wait, there is more!

Slide 40

Slide 40 text

final class NotifyAttendee { public function handle(RegistrationCreated $registrationCreated): void { $registration = $registrationCreated->getRegistration(); mail( $registration->getEmailAddressAsString(), ‘See you soon’, sprintf( ‘Hi %s
See you soon at %s ‘, $registration->getFirstName(), $registration->getMeetingName() ); } }

Slide 41

Slide 41 text

final class NotifyAttendeeByMandrill { public function handle(RegistrationCreated $registrationCreated): void { $registration = $registrationCreated->getRegistration(); $mandrill new Mandrill('/* */'); $mandrillMessage = [ /* */ ]; try { $mandrill->messages->send($mandrillMessage); } catch(Mandrill_Error $e) { // } }

Slide 42

Slide 42 text

Oh no! The service is down

Slide 43

Slide 43 text

final class NotifyAttendeeJob { public function __construct(AsyncMessageBus $bus) { $this->bus = $bus; } public function handle(RegistrationCreated $registrationCreated): void { $registration = $registrationCreated->getRegistration(); $message new SendConfimationMessage($registration); $this->bus->send($message); }

Slide 44

Slide 44 text

final class SendConfimationMessageWorker { public function handle(SendConfimationMessage $message): void { $mandrill new Mandrill('/* */'); $mandrillMessage = [ /* */ ]; try { $mandrill->messages->send($mandrillMessage); $mandrillMessage->markSend(); } catch(Mandrill_Error $e) { // } }

Slide 45

Slide 45 text

Takeaways Locate and isolate Bounded contexts and turn them into modules Let the world know what changed in an Event Driven plugin architecture

Slide 46

Slide 46 text

Joop Lammerts Website: www.procurios.com Twitter: @jlammerts