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

Technically DDD v7

Avatar for pelshoff pelshoff
December 02, 2018

Technically DDD v7

Avatar for pelshoff

pelshoff

December 02, 2018
Tweet

More Decks by pelshoff

Other Decks in Programming

Transcript

  1. final class Name { private $lastName; private $firstName; private $insertion;

    public function __construct(string $lastName, string $firstName, string $insertion) { $this->lastName = $lastName; $this->firstName = $firstName; $this->insertion = $insertion; $this->nameMustHaveLastName(); } private function nameMustHaveLastName(): void { if (!$this->lastName) { throw InvalidName::becauseLastNameIsMissing(); } } public function getLastName(): string { /**/ } public function getFirstName(): string { /**/ } public function getInsertion(): string { /**/ } }
  2. final class EmailAddress { private $emailAddress; public function __construct(string $emailAddress)

    { $this->emailAddress = $emailAddress; $this->emailAddressMustBeAnActualEmailAddress(); } private function emailAddressMustBeAnActualEmailAddress(): void { if (!filter_var($this->emailAddress, FILTER_VALIDATE_EMAIL)) { throw InvalidEmailAddress::becauseThisIsNotAnEmailAddress(); } } public function asString(): string { return $this->emailAddress; } }
  3. Value objects • Express a value • Define allowed values

    • Are immutable • Are easy to test
  4. final class Attendee { private $id; private $name; private $emailAddress;

    public function __construct(Uuid $id, Name $name, EmailAddress $emailAddress) { $this->id = $id; $this->name = $name; $this->emailAddress = $emailAddress; } public function getId(): Uuid { /**/ } public function getName(): Name { /**/ } public function setName(Name $name): Attendee { /**/ } public function getEmailAddress(): EmailAddress { /**/ } public function setEmailAddress(EmailAddress $emailAddress): Attendee { $this->emailAddress = $emailAddress; return $this; } }
  5. Entities • Have identity • Are more than their attributes

    • Evolve over time • Are slightly harder to test
  6. final class Registration { private $repository; public function __construct(AttendeeRepository $repository)

    { $this->repository = $repository; } public function registerNew(Attendee $attendee): void { $this->emailAddressMustBeUnique($attendee->getEmailAddress()); $this->repository->add($attendee); } private function emailAddressMustBeUnique(EmailAddress $emailAddress): void { try { $this->repository->findByEmailAddress($emailAddress); } catch (AttendeeNotFound $e) { return; } throw RegistrationFailed::becauseEmailAddressIsNotUnique(); } }
  7. Services • Have no identity or attributes (Are not a

    “thing”) • Tackle cross-concern operations • Are harder to test
  8. final class Meeting { private $meetingId; private $title; private $description;

    private $code; private $startDate; private $endDate; private $startTime; private $endTime; private $isPublished; private $subTitle; private $program; public function __construct(Uuid $meetingId, string $title, string $description, string $code, string $startDate, string $endDate, string $startTime, string $endTime, bool $isPublished, string $subTitle, array $program) { $this->meetingId = $meetingId; $this->title = $title; // ... } }
  9. new Meeting( Uuid::generate(), 'GrowIT 2018', '...', '#growITconf', '2018-12-01', '2018-12-02', '09:00',

    '18:00', false, 'Full Stack Conference', [ [ 'date' => '2018-12-02', 'startTime' => '15:15', 'endTime' => '16:00', 'title' => 'Technically DDD', 'room' => 'Track A', ], [ 'date' => '2018-12-02', 'startTime' => '16:00', 'endTime' => '16:45', 'title' => 'Turning Grey Hairs to Your Advantage', 'room' => 'Track C', ], ] );
  10. final class Meeting { public function __construct(/**/) { // ..

    $this->meetingCannotEndBeforeStart(); } private function meetingCannotEndBeforeStart(): void { if ($this->startDate < $this->endDate) { return; } if ($this->startDate > $this->endDate) { throw InvalidMeeting::becauseMeetingEndsBeforeStarting(); } if ($this->startTime > $this->endTime) { throw InvalidMeeting::becauseMeetingEndsBeforeStarting(); } } }
  11. final class Meeting { private $meetingId; private $title; private $description;

    private $code; private $duration; private $isPublished; private $subTitle; private $program; public function __construct(Uuid $meetingId, string $title, string $description, string $code, MeetingDuration $duration, bool $isPublished, string $subTitle, array $program) { $this->meetingId = $meetingId; $this->title = $title; $this->description = $description; $this->code = $code; $this->duration = $duration; $this->isPublished = $isPublished; $this->subTitle = $subTitle; $this->program = $program; } }
  12. final class MeetingDuration { public function __construct(string $startDate, string $endDate,

    string $startTime, string $endTime) { $this->startDate = $startDate; $this->endDate = $endDate; $this->startTime = $startTime; $this->endTime = $endTime; $this->meetingCannotEndBeforeStart(); } private function meetingCannotEndBeforeStart(): void { if ($this->startDate < $this->endDate) { return; } if ($this->startDate > $this->endDate) { throw InvalidMeetingDuration::becauseDurationEndsBeforeStarting(); } if ($this->startTime > $this->endTime) { throw InvalidMeetingDuration::becauseDurationEndsBeforeStarting(); } } }
  13. final class MeetingDuration { private $start; private $end; public function

    __construct(DateTimeImmutable $start, DateTimeImmutable $end) { $this->start = $start; $this->end = $end; $this->meetingCannotEndBeforeStart(); } private function meetingCannotEndBeforeStart(): void { if ($this->start > $this->end) { throw InvalidMeetingDuration::becauseDurationEndsBeforeStarting(); } } }
  14. new Meeting( Uuid::generate(), 'GrowIT 2018', '...', '#growITconf', new MeetingDuration( new

    DateTimeImmutable('2018-12-01' . ' ' . '09:00'), new DateTimeImmutable('2018-12-02' . ' ' . '18:00') ), false, 'Full Stack Conference', [ [ 'date' => '2018-12-02', 'startTime' => '15:15', 'endTime' => '16:00', 'title' => 'Technically DDD', 'room' => 'Track A', ], [ 'date' => '2018-12-02', 'startTime' => '16:00', 'endTime' => '16:45', 'title' => 'Turning Grey Hairs to Your Advantage', 'room' => 'Track C', ], // ..
  15. [ [ 'date' => '2018-12-02', 'startTime' => '15:15', 'endTime' =>

    '16:00', 'title' => 'Technically DDD', 'room' => 'Track A', ], [ 'date' => '2018-12-02', 'startTime' => '16:00', 'endTime' => '16:45', 'title' => 'Turning Grey Hairs to Your Advantage', 'room' => 'Track C', ], ]
  16. private function programSlotsCannotOccurInTheSameRoomAtTheSameTime(): void { foreach ($this->program as $index =>

    $slot) { foreach (array_slice($this->program, $index + 1) as $comparison) { if ($slot['room'] !== $comparison['room']) { continue; } if ($slot['date'] !== $comparison['date']) { continue; } if ($slot['startTime'] >= $comparison['endTime']) { continue; } if ($slot['endTime'] <= $comparison['startTime']) { continue; } throw InvalidProgram::becauseProgramSlotsOverlap(); } } }
  17. final class Meeting { private $meetingId; private $title; private $description;

    private $code; private $duration; private $isPublished; private $subTitle; private $program; public function __construct(Uuid $meetingId, string $title, string $description, string $code, MeetingDuration $duration, bool $isPublished, string $subTitle, Program $program){ $this->meetingId = $meetingId; $this->title = $title; $this->description = $description; $this->code = $code; $this->duration = $duration; $this->isPublished = $isPublished; $this->subTitle = $subTitle; $this->program = $program; } }
  18. final class Program { // .. private function programSlotsCannotOccurInTheSameRoomAtTheSameTime(): void

    { foreach ($this->program as $index => $slot) { foreach (array_slice($this->program, $index + 1) as $comparison) { if ($slot['room'] !== $comparison['room']) { continue; } if ($slot['date'] !== $comparison['date']) { continue; } if ($slot['startTime'] >= $comparison['endTime']) { continue; } if ($slot['endTime'] <= $comparison['startTime']) { continue; } throw InvalidProgram::becauseProgramSlotsOverlap(); } } } // ..
  19. final class Program { private $program; /** @param Slot[] $program

    */ public function __construct(array $program) { $this->program = $program; $this->programSlotsCannotOccurInTheSameRoomAtTheSameTime(); } private function programSlotsCannotOccurInTheSameRoomAtTheSameTime(): void { foreach ($this->program as $index => $thisSlot) { foreach (array_slice($this->program, $index + 1) as $thatSlot) { if ($thisSlot->overlapsWith($thatSlot)) { throw InvalidProgram::becauseProgramSlotsOverlap(); } } } } }
  20. final class Slot { private $duration; private $title; private $room;

    public function __construct(MeetingDuration $duration, string $title, string $room) { $this->duration = $duration; $this->title = $title; $this->room = $room; } public function overlapsWith(Slot $that): bool { return $this->room === $that->room && $this->duration->overlapsWith($that->duration); } }
  21. final class MeetingDuration { private $start; private $end; public function

    __construct(DateTimeImmutable $start, DateTimeImmutable $end) { /**/ } private function meetingCannotEndBeforeStart(): void { /**/ } public function overlapsWith(MeetingDuration $that): bool { return $this->start >= $that->start && $this->start <= $that->end || $this->end >= $that->start && $this->end <= $that->end || $that->start >= $this->start && $that->start <= $this->end || $that->end >= $this->start && $that->end <= $this->end; } }
  22. final class MeetingDuration { private $start; private $end; public function

    __construct(DateTimeImmutable $start, DateTimeImmutable $end) { /**/ } private function meetingCannotEndBeforeStart(): void { /**/ } public function overlapsWith(MeetingDuration $that): bool { return $that->contains($this->start) || $that->contains($this->end) || $this->contains($that->start) || $this->contains($that->end); } private function contains(DateTimeImmutable $date): bool { return $date >= $this->start && $date <= $this->end; } }
  23. final class MeetingDuration { private $start; private $end; public function

    __construct(DateTimeImmutable $start, DateTimeImmutable $end) { /**/ } private function meetingCannotEndBeforeStart(): void { /**/ } public function overlapsWith(MeetingDuration $that): bool { return !($this->start > $that->end || $that->start > $this->end); } }
  24. final class MeetingDuration { private $start; private $end; public function

    __construct(DateTimeImmutable $start, DateTimeImmutable $end) { /**/ } private function meetingCannotEndBeforeStart(): void { /**/ } public function overlapsWith(MeetingDuration $that): bool { return !$this->before($that) && !$that->before($this); } private function before(MeetingDuration $that): bool { return $that->start > $this->end; } }
  25. [ 'date' => '2018-12-02', 'startTime' => '15:15', 'endTime' => '16:00',

    'title' => 'Technically DDD', 'room' => 'Track A', ], private function before(MeetingDuration $that): bool { return $that->start > $this->end; }
  26. final class SlotDuration { private $start; private $end; public function

    __construct(DateTimeImmutable $start, DateTimeImmutable $end) { /**/ } private function slotCannotEndBeforeStart(): void { /**/ } private function slotMustStartAndEndOnTheSameDay(): void { if ($this->start->diff($this->end)->days >= 1) { throw InvalidSlotDuration::becauseSlotEndsOnDifferentDay(); } } public function overlapsWith(SlotDuration $that): bool { return !$this->before($that) && !$that->before($this); } private function before(SlotDuration $that): bool { return $that->start >= $this->end; } }
  27. new Meeting( Uuid::generate(), 'GrowIT 2018', '...', '#growITconf', '2018-12-01', '2018-12-02', '09:00',

    '18:00', false, 'Full Stack Conference', [ [ 'date' => '2018-12-02', 'startTime' => '15:15', 'endTime' => '16:00', 'title' => 'Technically DDD', 'room' => 'Track A', ], [ 'date' => '2018-12-02', 'startTime' => '16:00', 'endTime' => '16:45', 'title' => 'Turning Grey Hairs to Your Advantage', 'room' => 'Track C', ], ] );
  28. new Meeting( Uuid::generate(), 'GrowIT 2018', '...', '#growITconf', new MeetingDuration( new

    DateTimeImmutable('2018-12-01' . ' ' . '09:00'), new DateTimeImmutable('2018-12-02' . ' ' . '18:00') ), false, 'Full Stack Conference', new Program([ new Slot( new SlotDuration( new DateTimeImmutable('2018-12-02' . ' ' . '15:15'), new DateTimeImmutable('2018-12-02' . ' ' . '16:00') ), 'Technically DDD', 'Track A' ), new Slot( new SlotDuration( new DateTimeImmutable('2018-12-02' . ' ' . '16:00'), new DateTimeImmutable('2018-21-02' . ' ' . '16:45') ), 'Turning Grey Hairs to Your Advantage', 'Track C' ), ]) // ..