Save 37% off PRO during our Black Friday Sale! »

Domain modeling with PHP / domain-modeling-with-php-en

Domain modeling with PHP / domain-modeling-with-php-en



October 17, 2021


  1. Domain modeling with PHP 2021/10/17 @shin1x1

  2. @shin1x1 Masashi Shinbara Freelance Web Developer Podcast(Japanese)

  3. Sample code

  4. Miscommunication in software development 4

  5. ex. Same word, different concept The same concept can be

    expressed with different words. 5
  6. ex. Some features dropped out in conversation 6

  7. ex. Cannot recall the original concept from code 7

  8. Let's create shared concept! 8

  9. Let's create shared concept! 9

  10. Domain model Domain: The target domain that the software deals

    with. In the case of a business application, the target business is the domain. Model: A concept that extracts the elements necessary for a purpose from a target. 10
  11. Domain model A concept that extracts the necessary elements from

    a domain. Sometimes the necessary concepts are created or modified. Usually does not include the technical elements of the system. (Except when the technical element is a domain.) 11
  12. Today's domain 12

  13. The domain in this announcement is fictitious and has nothing

    to do with the real one. 13
  14. Vaccination System Vaccinations will be administered at the vaccination site

    after prior appointment. Vaccination is currently given only once. Establish a system for making vaccination reservations and registering vaccinees. 14
  15. Vaccination flow 15

  16. Modeling 16

  17. Modeling Extract the elements of the domain required for systemization.

    Gradually shape them using multiple perspectives and methods. Macro perspective Conceptual model diagram, use case diagram Micro perspective Glossary, use case scenarios, code Obtain feedback from the business team to improve the accuracy. 17
  18. Use case diagrams Indicates the relationship between users and system-provided

    functions. Indicate the scope of systemization (what to include in the system and what not to include). Local governments and doctors are not users of this system. 18
  19. Create glossary Compile a table of domain terms from business

    and requirement documents, interviews, etc. Terms, meanings, constraints, etc. Give the same name to the same concept. Important for communication. If there is another name, include it. Include not only nouns, but also actions and events. 19
  20. Example of glossary terms content recipient A person who receives

    a vaccination. reserve The act of reserving a vaccination by a recipient. Only unreserved recipients can be reserved. reserved date The date of vaccination. The date of the vaccination is specified by the recipient in the reservation. After 7 days but within 30 days from the date of reservation registration. 20
  21. Create a conceptual model (domain model) diagram Arrange the terms

    in the glossary as a simple class diagram. Organize the relationships between each term by connecting them with lines or grouping them. Look at the terms from a bird's eye view to see if there are any discrepancies or omissions. 21
  22. Example of conceptual model diagram 22

  23. Implement and verify in code 23

  24. Implement and verify in code Implemented in code as part

    of modeling. Implement in code as part of modeling. 24
  25. Implement domain model 1 concept = 1 class. By making

    it a class, you can benefit from type checking. Implemented in POPO (Plain Old PHP Object). Use domain model terminology for class and method names. Confine the constraints of the model to the implementation of the class. Make it an immutable object. Do not create setter methods. Change property values according to domain logic. (ex. use reserve() instead of setReservation()) 25
  26. Vaccination ticket code class Implement the constraint of the vaccination

    ticket no(10 digits) in the constructor. Instantiation = constraint is satisfied. final class VaccinationTicketNo { public function __construct(private string $code) { if (preg_match('/\A[0-9]{10}\z/', $code) ! == 1) { throw new InvariantException('Invalid code:' . $code); } } } 26
  27. Test the vaccination ticket code class POPO, so easy to

    test. Make sure that exceptions are thrown if constraints are violated. /** * @test */ public function construct_invalid_code(): void { $this->expectException(InvariantException::class); new VaccinationTicketNo('A234567890'); } 27
  28. Reserved date class Takes a string indicating the reserved date

    and the current date in the Factory method, and validates the domain rule. (After 7 days, but within 30 days) final class ReservedDate { public function __construct(private Date $date) { } public static function createFromString(string $dateString, Date $now): self { $date = Date::createFromString($dateString); // TODO: throw an exception if $now is not within 30 days after 7 days return new self($date); } } 28
  29. RecipientId class Indicates the identifier of the recipient. Identifiers are

    often the arguments of methods, and benefit greatly from type checking. Identifiers are often used as arguments to methods, and benefit from type checking. Since other IDs in a project are often implemented in the same way, it is easier to implement them if they are standardized using traits. final class RecipientId { use SequencialId; } 29
  30. Recipient class Has properties for recipient ID, appointment, and inoculation.

    The constraints of each value are implemented in each class. Reservation and vaccination are set to nullable because they may not have values. final class Recipient { public function __construct( private RecipientId $id, private ?Reservation $reservation = null, private ?Vaccination $vaccination = null, ) { } 30
  31. Recipient class - reserve method Implement the domain logic of

    registering an appointment in the method. If there is already a reservation, it is assumed that the reservation is complete and an exception is thrown. Create and return a new instance containing the reservation. public function reserve(Reservation $reservation): self { if ($this->reservation ! == null) { throw new PreconditionException(); } return new self( $this->id, $reservation, ); } 31
  32. Test the reserve method Check that the new instance created

    by the register-reservation method contains a reservation. /** * @test */ public function reserve() { $sut = new Recipient(new RecipientId()); $reservation = new Reservation( new ReservedDate(Date::createFromString('2021-09-19'))); $actual = $sut->reserve($reservation); $expected = new Recipient(new RecipientId(), reservation: $reservation, vaccination: null); $this->assertEquals($expected, $actual); } 32
  33. Implementation as part of modeling Modeling and implementing the code

    gives you a better understanding. It eliminates ambiguity so you can find inconsistencies and flaws in the model. In fact, you often notice them by implementing them in code (right?). It can be implemented in code. Can be executed and verified in tests. The advantage of modeling by someone who can write code. 33
  34. Verify with use cases 34

  35. Validate the model in use cases Validation using domain models

    in use case scenarios. Use case description Describe the use case scenario in text. 35
  36. Example of use case description - reserve Main actors Recipient

    Preconditions (conditions to be fulfilled before the scenario execution) The recipient has a vaccination ticket. The recipient has not completed the reservation or the vaccination. The recipient has neither an reservation nor a completed vaccination. Post-conditions (conditions to be fulfilled after the scenario execution) The recipient's reservation is registered. 36
  37. Basic flow The recipient accesses the reservation screen. The recipient

    enters and sends the inoculation ticket number, the municipality number, and the reserved inoculation date. The system identifies the recipient from the vaccination ticket code and the municipality number. The system registers the reservation. 37
  38. Alternative flow (other than basic flow, abnormal flow) In the

    following cases, an input error shall occur. There is no recipient corresponding to the vaccination ticket code or municipality number. The corresponding recipient has already made an appointment or completed the inoculation. The reserved date is beyond the range of 7 days to 30 days from the current date. 38
  39. Use case scenario implementation 1 use case scenario = 1

    class. Only one public method is used for scenario execution. Domain-related processing is implemented by domain objects. Implement with POPO to limit the responsibilities and make testing easier. Processing related to IO such as database are abstracted by interfaces. 39
  40. Example of IO abstraction with interfaces 40

  41. Implement the reserved registration use case class Create and retrieve

    domain objects and execute the domain logic. Save the object updated by the domain logic to the database. final class ReservationUseCase { public function run(VaccinationTicketNo $vaccinationTicketNo, MunicipalityNo $municipalityNo, ReservedDate $reservedDate): void { // Get the domain object from the database $recipient = $this->query->find($vaccinationTicketNo, $municipalityNo); if ($recipient === null) { throw new PreconditionException('The recipient does not exist'); } // Execute the domain logic $recipient = $recipient->reserve(new Reservation($reservedDate)); // Store the resulting domain object in the database $this->command->store($recipient); } } 41
  42. Model, code improvements 42

  43. Vaccination status determination Determination of recipient status, such as reserved

    or inoculated, is based on the value of the reservation or vaccination property. It is possible to have an invalid state where the reservation has no value and the vaccination has a value. As the number of variations in the recipient status increases, the pattern of transitions also increases. Add an recipient status that clearly indicates it. 43
  44. Conceptual model diagram Add inoculation status to the domain model.

  45. State transitions for recipient status State transition rules are shown

    in activity diagrams to clarify transition patterns. Transitions that are not indicated in the diagram will result in an error. 45
  46. Implementation of inoculation status. Representation of states with enums (introduced

    in PHP 8.1). Enums can be treated as types. enum VaccinationStatus { case Unreserved; case Reserved; case Vaccinated; } 46
  47. Change the Recipient class. Add recipient status in constructor. Initial

    value should be unreserved. final class Recipient { public function __constructor( private VaccinatorId $id, private VaccinationStatus $recipientStatus = VaccinationStatus::Unreserved, private ?Reservation $reservation = null, private ?Vaccination $vaccination = null, ) {} 47
  48. Change Recipient class - reserve method Do pre-condition validation for

    reserve by looking at the recipient status. Changed cancel reservation and vaccinate methods as well. public function reserve(Reservation $reservation): self { if ($this->reservationStatus ! == ReservationSttus::Unreserved) { throw new PreconditionException('Cannot reserve with current status'); } return new self( $this->id, ReservationStatus::Reserved, $reservation, ); } 48
  49. No change to the reserved use case No change in

    use case classes, since only domain classes are changed. final class ReservationUseCase { public function run(VaccinationTicketNo $vaccinationTicketNo, MunicipalityNo $municipalityNo, ReservedDate $reservedDate): void { // Get the domain object from the database $recipient = $this->query->find($vaccinationTicketNo, $municipalityNo); if ($recipient === null) { throw new PreconditionException('The recipient does not exist'); } // Execute the domain logic $recipient = $recipient->reserve(new Reservation($reservedDate)); // Store the resulting domain object in the database $this->command->store($recipient); } } 49
  50. Summary 50

  51. Summary Construct a domain model by extracting the necessary elements

    from the domain. Share the domain model as a common concept. Implement the vocabulary and knowledge of the domain model into domain classes. Try modeling because you can write code!!! 51
  52. I'm looking forward to your feedback! 52

  53. Where do I start? Start with a glossary. Just creating

    a glossary and aligning your perceptions will help you understand a lot. People who will join the project later will also be happy. And maybe a use case diagram. 53
  54. How to maintain diagrams and documents? Of course, it is

    ideal to do so. There is also the idea of as a communication tool, leaving it as a snapshot on a wiki, etc. If you use PlantUML, you can give up the layout adjustment :) 54
  55. How to maintain diagrams and documents? The implementation of the

    domain model is also a component in the overall system, so implement it as needed. There are constraints and logic of the domain. There are domain constraints and logic. You want to indicate with something without logic, such as an identifier. If you want to do type checking. If not, a scalar type is fine. 55
  56. References Domain-Driven Design: Tackling Complexity in the Heart of Software Domain Modeling Made Functional: Tackle Software Complexity with Domain- Driven Design and F# Object oriented Model (Japanese) The story of visualizing an existing system through modeling (Japanese) nilin-ndahua 56