Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Refactor Like a Pro => Clean Code meets DDD

Refactor Like a Pro => Clean Code meets DDD

Avatar for Silas Joisten

Silas Joisten

August 20, 2026

More Decks by Silas Joisten

Other Decks in Programming

Transcript

  1. R E FA C T O R L I K

    E A P R O CLEAN CODE 🤝 DDD
  2. E V ECRO LDOE OAKNE DD TATH OSUOGMHET…O L D

    … W H O T H E 🤬 W R OT E T H I S ?
  3. 🕵️ S O Y O U S TA R T

    I N V E S T I G AT I N G … 1 git blame -L 42,42 src/Service/ExportService.php
  4. W H Y CT OO DUEN DB EE CR SO

    TA M ENSD H A R D
  5. 🕵️‍♂️ Cryptic naming 🧙 Clever tricks instead of clear logic

    🧩 Over-abstraction 🍝 Mixing business logic, DB calls, and view rendering ❓ Lack of comments, weird edge cases
  6. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20 21 // ... class PriceHandler { private $r, $s; public function __construct($r, $s) { $this->r = $r; $this->s = $s; } public function handle($d) { $u = $this->r->get($d['i']); if (!$u['p']) { $u['v'] = $u['v'] * 1.19; $this->s->val($u); } return $u; } }
  7. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 // ... final class PriceHandlerTest extends TestCase { #[Test] public function aPromotionalSubscriptionSkipsVat(): void { $repository = self::createMock(SubscriptionRepository::class); $formatter = self::createMock(PricingFormatterService::class); $repository->method('get')->willReturn(['p' => true, 'v' => 99]); $formatter->expects($this->once())->method('val'); $subscription = (new PriceHandler($repository, $formatter))->handle(['i' => 5]); 15 16 17 18 self::assertSame('0,99€', $subscription['v']); self::assertTrue($subscription['p']); } }
  8. STEP 2 U S E N ATF EI AT V

    E ULRAENS G U A G E
  9. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 // ... final readonly class PriceHandler { public function __construct( private SubscriptionRepository $r, private PricingFormatterService $s ) { } public function handle(int $d): array { $u = $this->r->get($d); if (!$u['p']) { $u['v'] = $u['v'] * 1.19; $this->s->val($u); } return $u; } }
  10. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 // ... final readonly class PriceHandler { public function __construct( private SubscriptionRepository $subscriptionRepository, private PricingFormatterService $pricingFormatter ) { } public function applyVat(int $subscriptionId): array { $subscription = $this->subscriptionRepository->get($subscriptionId); if (!$subscription['isPromotion']) { $subscription['price'] = $subscription['price'] * 1.19; $this->pricingFormatter->format($subscription); 16 17 18 19 } return $subscription; } }
  11. 1 2 // ... final readonly class PriceHandler 3 4

    5 6 7 8 9 10 11 12 13 14 15 16 17 18 { public function __construct( private SubscriptionRepository $subscriptionRepository, private PricingFormatterService $pricingFormatter, ) { } /** * Germany's VAT for our Subscriptions is 19% * Subscriptions that are promotions do not have VAT applied! * * @return array{isPromotion: bool, price: string} */ public function applyVat(int $subscriptionId): array { /** @var array{isPromotion: bool, price: string} $subscription */ 19 20 $subscription = $this->subscriptionRepository->get($subscriptionId); 21 22 if (!$subscription['isPromotion']) { $subscription['price'] = $subscription['price'] * 1.19; 23 24 25 $this->pricingFormatter->format($subscription); } 26 27 28 return $subscription; } }
  12. T H I S S T I LR LI GDHOT

    E😩S N ’ T F E E L
  13. L E T ’ S TA L K A B

    O U T T H E D O M A I N ❓ What is a Subscription in my domain ❓ What is a Price in my domain ❓ What does it mean to be a promotional subscription ❓ What does it mean to change a subscription’s price
  14. T H E A R T I FA C T

    S O F D D D WORD DESCRIPTION Entity Unique object with an identity Value Object Data with no identity, defined by attributes Aggregate Group of related entities treated as a single unit Relationship How content entities connect Bounded Context Defines the limits of a domain
  15. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 // ... final readonly class PriceHandler { public function __construct( private SubscriptionRepository $subscriptionRepository, private PricingFormatterService $pricingFormatter, ) { } /** * Germany's VAT for our Subscriptions is 19% * Subscriptions that are promotions do not have VAT applied! * * @return array{isPromotion: bool, price: string} */ public function applyVat(int $subscriptionId): array { /** @var array{isPromotion: bool, price: string} $subscription */ $subscription = $this->subscriptionRepository->get($subscriptionId); 20 21 22 if (!$subscription['isPromotion']) { $subscription['price'] = $subscription['price'] * 1.19; 23 24 $this->pricingFormatter->format($subscription); } 25 26 27 28 return $subscription; } }
  16. VA L U E O B J E C T

    S 1 2 3 4 5 6 7 8 final readonly class SubscriptionId { public function __construct( public int $value, ) { Assert::positiveInteger($id, 'Subscription ID must be a positive integer'); } } Requires webmozart/assert library. Assert::xxx throws an \InvalidArgumentException if the condition is not met.
  17. VA L U E O B J E C T

    S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 final readonly class Price { public function __construct( public int $value, public string $currency = '€', ) { Assert::positiveInteger($value); Assert::stringNotEmpty($currency); Assert::notWhitespaceOnly($currency); } public function toString(): string { return sprintf('%01.2f%s', $this->value / 100.0, $this->currency); } }
  18. ENTITIES 1 2 3 4 5 6 7 8 9

    10 11 12 13 14 final readonly class Subscription { public function __construct( public SubscriptionId $id, public Price $price, private bool $isPromotion = false, ) { } public function withPrice(Price $price): self { return new self($this->id, $this->name, $price, $this->isPromotion); } }
  19. SERVICES 1 2 3 4 5 6 7 8 9

    10 11 12 13 14 15 final readonly class VatHandler { /** * Germany's VAT for our Subscriptions is 19% * Subscriptions that are promotions do not have VAT applied! */ public function forGermany(Subscription $subscription): Subscription { if ($subscription->isPromotion) { return $subscription; } return $subscription->withPrice(new Price($subscription->price * 1.19)); } }
  20. CONTROLLERS 1 final class SubscriptionController extends AbstractController 2 { 3

    #[Route('/subscriptions/{id}/price')] 4 public function index(int $id, VatHandler $vat, SubscriptionRepository $subscriptions): Response 5 { 6 $subscription = $vat->forGermany($subscriptions->byId(new SubscriptionId($id))); 7 8 // do other subscription operations 9 10 return $this->json(['price' => $subscription->price->toString()]); 11 12 } }
  21. DDD MAKES YOUR CODE AI-READY 🧠 Rich domain model =

    real context, not guesses 📐 Value Objects & Entities show how classes should look 🎯 Clear Bounded Context keeps suggestions on-topic ✅ Explicit invariants let AI write tests it understands
  22. T O O L S T H AT H E

    L P P H P S TA N – S TAT I C A N A LY S I S R E C TO R – A U TO M AT E D R E FACTO R I N G S Y M F O N Y I N S I G H T – P R O J E CT H E A LT H PHP-CS FIXER – CODE STYLE
  23. K E Y TA K E AWAY S Code is

    communication — write it for other humans Your future self is just another dev with no context Maintainable code is an act of empathy 🫶
  24. T H A N K Y O U ! ❤️

    ANY QUESTIONS?