Slide 1

Slide 1 text

Przekraczanie granic Bounded Contextów 🗺 PHPers Trójmiasto 2025 - Damian Dziaduch

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Czym w ogóle jest Bounded Context? 🧐

Slide 7

Slide 7 text

Bezpośrednie odwołanie 🤝

Slide 8

Slide 8 text

Order Submission Order Management

Slide 9

Slide 9 text

namespace Acme\ProjectX\OrderSubmission\Application\CommandBus; final class SendOrderCommandHandler { public function __ construct( private readonly \Acme\ProjectX\OrderManagement\Application\Port\Out\OrderRepository $orders, private readonly \Acme\ProjectX\OrderSubmission\Application\Port\Out\SendOrderPort $sendOrder, private readonly \Psr\EventDispatcher\EventDispatcherInterface $eventDispatcher, ) { } public function __ invoke(SendOrderCommand $command): void { $order = $this -> orders -> getById(new OrderId($command -> orderId())); try { $this -> sendOrder - > send($order); } catch (UnableToSendOrder $exception) { $this -> dispatchFailedEvent($order, $exception - > getMessage()); return; } $this -> eventDispatcher -> dispatch(OrderSentToProductionEvent :: fromOrder($order)); }

Slide 10

Slide 10 text

Plusy Bezpośrednie odwołanie 🤝 • Trywialne w implementacji 🥷 • Świetne na początek gdy granice kontekstów nie są jasne 🧐

Slide 11

Slide 11 text

Minusy Bezpośrednie odwołanie 🤝 • "Betonuje" implementację 🧱 • "Nie da się" wyciągnąć kontekstu do mikro serwisu 🔒

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

$> deptrac analyse --con f ig- f ile=deptrac-horizontal.yaml 1258/1258 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% ----------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Reason OrderSubmission ----------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Violation Acme\ProjectX\OrderSubmission\Application\CommandBus\SendOrderCommandHandler must not depend on Acme\ProjectX\OrderManagement\Application\Port\Out\OrderRepository (OrderManagement) /opt/project/srcHexagonal/OrderSubmission/Application/CommandBus/SendOrderCommandHandler.php:19 Violation Acme\ProjectX\OrderSubmission\Application\CommandBus\SendOrderCommandHandler must not depend on Acme\ProjectX\OrderManagement\Application\Port\Out\OrderRepository (OrderManagement) /opt/project/srcHexagonal/OrderSubmission/Application/CommandBus/SendOrderCommandHandler.php:10 ----------- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Slide 14

Slide 14 text

Shared Kernel 🏛

Slide 15

Slide 15 text

Order Submission Order Management Shared Kernel

Slide 16

Slide 16 text

namespace Acme\ProjectX\OrderSubmission\Application\CommandBus; final class SendOrderCommandHandler { public function __ construct( private readonly \Acme\ProjectX\SharedKernel\Application\Port\Out\OrderRepository $orders, private readonly \Acme\ProjectX\OrderSubmission\Application\Port\Out\SendOrderPort $sendOrder, private readonly \Psr\EventDispatcher\EventDispatcherInterface $eventDispatcher, ) { } public function __ invoke(SendOrderCommand $command): void { $order = $this -> orders -> getById($command -> orderId()); try { $this -> sendOrder - > send($order); } catch (UnableToSendOrder $exception) { $this -> dispatchFailedEvent($order, $exception - > getMessage()); return; } $this -> eventDispatcher -> dispatch(OrderSentToProductionEvent :: fromOrder($order)); }

Slide 17

Slide 17 text

• Dość proste w implementacji 🥷 • Brak bezpośredniej integracji między kontekstami ✋ Shared Kernel 🏛 Plusy

Slide 18

Slide 18 text

Minusy • Im więcej klientów kodu z Shared Kernel tym trudniej go zmienić 🧱 • Wyciąganie Shared Kernel do biblioteki współdzielonej może być wyzwaniem 😓 Shared Kernel 🏛

Slide 19

Slide 19 text

Zdarzenie asynchroniczne 📮

Slide 20

Slide 20 text

Order Submission Order Management EventBus

Slide 21

Slide 21 text

{ "$schema": "http: / / json-schema.org/draft-07/schema", "$id": "http: // acme.com/events/order/order-created.json", "type": "object", "title": "events/order/order-created", "description": "Core orders: order created.", "required": [ "order_id", "merchant_id", "print_provider_id" ], "properties": { "order_id": { "type": "int", "min": 1 }, "merchant_id": { "type": "int", "min": 1 }, "print_provider_id": { "type": "int", "min": 1 }, ... } }

Slide 22

Slide 22 text

namespace Acme\ProjectX\OrderSubmission\Application\CommandBus; final class SendOrderCommandHandler { public function __ construct( private readonly \Acme\ProjectX\OrderSubmission\Application\Port\Out\SendOrderPort $sendOrder, private readonly \Psr\EventDispatcher\EventDispatcherInterface $eventDispatcher, ) { } public function __ invoke(\Acme\EventBus\Orders\OrderCreated $event): void { try { $this -> sendOrder - > send($event -> orderDto); } catch (UnableToSendOrder $exception) { $this -> dispatchFailedEvent($order, $exception - > getMessage()); return; } $this -> eventDispatcher -> dispatch(OrderSentToProductionEvent :: fromOrder($order)); }

Slide 23

Slide 23 text

Plusy • Komunikacja w pełni asynchroniczna 📮 • Możliwość wyjęcia kontekstu do mikro serwisu 🐣 • Odporność na awarie 🏋 • Brak bezpośredniej komunikacji ✋ Zdarzenie asynchroniczne 📮

Slide 24

Slide 24 text

Minusy Zdarzenie asynchroniczne 📮 • Zdarzenia mogą być "tłuste" 🐷 • Eventual Consistency 😶🌫 • Potrzebny EventBus i infrastruktura do tego 🏭 • Więcej zachodu niż w przypadku Shared Kernel 😓

Slide 25

Slide 25 text

Sub request ☎

Slide 26

Slide 26 text

Order Submission Order Management EventBus Sub Request

Slide 27

Slide 27 text

namespace Acme\ProjectX\OrderSubmission\Application\CommandBus; final class SendOrderCommandHandler { public function __ construct( private readonly \Acme\ProjectX\OrderSubmission\Application\Port\Out\GetOrderPort $orders, private readonly \Acme\ProjectX\OrderSubmission\Application\Port\Out\SendOrderPort $sendOrder, private readonly \Psr\EventDispatcher\EventDispatcherInterface $eventDispatcher, ) { } public function __ invoke(\Acme\EventBus\Orders\OrderCreated $event): void { $order = $this -> orders -> getById($event -> orderId); try { $this -> sendOrder - > send($order); } catch (UnableToSendOrder $exception) { $this -> dispatchFailedEvent($order, $exception - > getMessage()); return; } $this -> eventDispatcher -> dispatch(OrderSentToProductionEvent :: fromOrder($order)); }

Slide 28

Slide 28 text

final class HttpGetOrder implements GetOrder { public function get(int $orderId): OrderDto { $request = $this -> requestFactory - > createRequest(method: 'GET', uri: sprintf('/api/orders/%s', $orderId)) - > withHeader('Accept', 'application/json') - > withHeader('Content-Type', 'application/json'); $response = $this -> client -> sendRequest($request); return $this -> serializer -> deserialize( data: $response -> getBody() -> getContents(), type: OrderDto : : class, format: 'json', ); } }

Slide 29

Slide 29 text

final class InternalApplicationHttpClient implements ClientInterface { public function sendRequest(PsrRequest $request): PsrResponse { $symfonyRequest = $this - > httpFoundationFactory -> createRequest($this -> psrRequestToPsrServerRequest($request)); $this -> urlMatcher -> setContext(new RequestContext($symfonyRequest -> getBaseUrl(), $symfonyRequest - > getMethod())); try { $urlMatch = $this -> urlMatcher -> match($symfonyRequest -> getPathInfo()); } catch (ExceptionInterface $e) { return $this -> responseFactory -> createResponse(404, $e -> getMessage()); } if (!array_key_exists('_controller', $urlMatch)) { return $this -> responseFactory -> createResponse(404, 'Route not found'); } $symfonyRequest - > attributes -> add(['_controller' => $urlMatch['_controller']]); $response = $this -> kernel -> handle($symfonyRequest, HttpKernelInterface : : SUB_REQUEST); return $this -> responseFactory - > createResponse($response -> getStatusCode()) - > withBody($this - > streamFactory -> createStream((string) $response - > getContent())); }

Slide 30

Slide 30 text

Plusy Sub Request ☎ • Chude zdarzenia 🪶 • Jeden runtime, jeden proces 🐇 • Możliwość wyjęcia kontekstu do mikro serwisu 🐣 • Odporność na awarie 🏋

Slide 31

Slide 31 text

Minusy Sub Request ☎ • Dużo roboty - potrzebne zdarzenie oraz endpoint 😓 • Potrzebny EventBus i infrastruktura do tego 🏭

Slide 32

Slide 32 text

Podsumowanie • Integruj BC tak by dało się je wyciągnąć do mikro serwisów 🐣 • Zacznij od Bezpośredniego odwołania 🤝 a następnie spróbuj • Shared Kernel 🏛 • Zdarzenie asynchroniczne 📮 • Sub Request ☎ • Modularny monolit f irst by mieć pewność że granice są poprawne ✅

Slide 33

Slide 33 text

O mnie W branży od 2011 Senior software engineer Public speaker Trener Zapraszam do siebie:

Slide 34

Slide 34 text

Refaktoryzacja do czystej architektury Warsztat całodniowy Symfony oraz Laravel