Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up for free
Microservice within a Monolith #phpday
Joop Lammerts
May 11, 2019
Programming
0
65
Microservice within a Monolith #phpday
Joop Lammerts
May 11, 2019
Tweet
Share
More Decks by Joop Lammerts
See All by Joop Lammerts
How to improve your team synergy w/The Attitude Model #DPC19
jlammerts
0
59
Microservice within a Monolith #devdays2019
jlammerts
0
55
Microservice within a Monolith v1
jlammerts
0
26
The Attitude Model
jlammerts
0
17
Improve your team synergy w/The Attitude model
jlammerts
0
220
Other Decks in Programming
See All in Programming
Haskellでオブジェクト指向プログラミング
koheisakata
0
170
Rust on Lambda 大きめCSV生成
atsuyokota
1
390
設計の考え方とやり方
masuda220
PRO
45
25k
SwiftUI+TCAに挑戦!NewsPicks iOSアプリのリアーキテクチャ/re-architecture-newspicks-ios-app-with-swiftui-and-tca
takehilo
0
360
アジャイルで始める データ分析基盤構築
nagano
1
850
How GitHub Supports Vim License Detection, The Five Years Journey
othree
1
320
atama plusの開発チームはどのように「不確実性」に向き合ってきたか〜2022夏版〜
atamaplus
3
600
レビュー駆動学習のススメ_StaPy#83
soogie
0
300
Recap CDN, Edge, WebAssembly | ワインと鍋.js#1
sadnessojisan
2
1.2k
VIMRC 2022
achimnol
0
120
閱讀原始碼 - 再戰十年的 jQuery
eddie
1
280
ちょっとつよい足トラ
logilabo
0
330
Featured
See All Featured
Building Applications with DynamoDB
mza
84
4.7k
Mobile First: as difficult as doing things right
swwweet
213
7.5k
Building Adaptive Systems
keathley
25
1.1k
Put a Button on it: Removing Barriers to Going Fast.
kastner
56
2.3k
Robots, Beer and Maslow
schacon
152
7.1k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
269
12k
Designing on Purpose - Digital PM Summit 2013
jponch
106
5.7k
What's new in Ruby 2.0
geeforr
336
30k
Build your cross-platform service in a week with App Engine
jlugia
219
17k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
107
16k
Fireside Chat
paigeccino
13
1.4k
A better future with KSS
kneath
226
16k
Transcript
Microservices within a Monolith #phpday @jlammerts
Joop Lammerts Developer @procurios for +3 years @jlammerts
Procurios Cluster
Procurios Cluster for context
Our Monolith
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
Usage
Usage • 2000 clients • 800.000 users • 500.000 visitors
an hour
None
Monolith
None
Microservices
Service #1 Service #2 Service #3
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
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
Modules
Modulair Monolith
Modulair Monolith • Bounded context with no dependencies on each
other • Information can be duplicated for each bounded context
None
What to do with your legacy Monolith?
Project |--- core |--- modules |--- cms |--- meeting |---
relation |--- user
Project |--- core |--- modules |--- cms |--- meeting ->
attendee -> meeting -> registration -> ticket |--- relation |--- user
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('/'); } }
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'); } // }
namespace Meeting\Registration; final class RegistrationController { public function register() {
$user = UserService::getCurrentUser(); if (!$user->isAuthenticated()) { HttpResponse::redirect('/login'); } // }
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(); }
User is now a bounded context
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('/'); } }
But wait, there is more!
public function register() { // $form = $this->getRegistrationForm(); $data =
$form->getData(); if (!$data['meetingId'] || !$data['ticketId'] || !$data['remark']) { return $form; } $statement = $pdo->prepare(" UPDATE `tickets` SET `sold` = 1 WHERE `id` = ? AND `sold` = 0 "); $statement->execute([ $data['ticketId'] ]); if ($statement->rowCount() !== 1) { return 'There are no tickets available'; } // }
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'; } // }
final class TicketService { public static function purchase(int $ticketId): Ticket
{ $connection = DB::getConnection(); $statement = $connection->prepare(" UPDATE `tickets` SET `sold` = 1 WHERE `id` = ? AND `sold` = 0 "); $statement->execute([ $data['ticketId'] ]); if ($statement->rowCount() !== 1) { throw CouldNotPurchaseTicket::becauseNoTicketsLef(); } return new Ticket($ticketId); } }
public function register() { // $statement = $pdo->prepare(" INSERT INTO
`attendee` SET `user_id` = ?, `first_name` = ?, `last_name` = ?, `meeting_id` = ?, `ticket_id` = ?, `remark` = ?, "); $statement->execute([ $user->getId(), $user->getFirstName(), $user->getName(), $data['meetingId'], $data['ticketId'], $data['remark'], ]); // }
public function register() { // $attendee = new Attendee( $user->getId(),
$user->getFirstName(), $user->getName() ); RegistrationService::register($data['meetingId'], $attendee, $ticket, $data['remark']); /* * send confirmation stuff */ HttpResponse::redirect('/'); }
Modules sent messages
final class RegistrationService { public static function register(int $meetingId, Attendee
$attendee, Ticket $ticket, string $remark): void { $repository = self::getRepository(); $registration = $repository->create($meetingId, $attendee, $ticket, $remark); $registrationCreated = new RegistrationCreated($registration); foreach (self::getListenerProvider()->getListenersForEvent($registrationCreated) as $listener) { $listener->handle($registrationCreated); } } }
final class NotifyAttendee { public function handle(RegistrationCreated $registrationCreated): void {
// send confirmation stuff } }
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()); RegistrationService::register($data['meetingId'], $attendee, $ticket, $data['remark']); HttpResponse::redirect('/'); } }
Take aways Locate and isolate Bounded contexts and turn them
into modules Let the world know what changed in an Event Driven plugin architecture
Joop Lammerts Website: www.procurios.com Twitter: @jlammerts Feedback: https://joind.in/talk/7c6ed