Slide 1

Slide 1 text

Domain modeling with PHP 2021/10/17 @shin1x1

Slide 2

Slide 2 text

@shin1x1 Masashi Shinbara Freelance Web Developer Podcast(Japanese) https://php-genba.shin1x1.com/

Slide 3

Slide 3 text

Sample code https://github.com/shin1x1/domain-modeling-with-php

Slide 4

Slide 4 text

Miscommunication in software development 4

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

ex. Some features dropped out in conversation 6

Slide 7

Slide 7 text

ex. Cannot recall the original concept from code 7

Slide 8

Slide 8 text

Let's create shared concept! 8

Slide 9

Slide 9 text

Let's create shared concept! 9

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Today's domain 12

Slide 13

Slide 13 text

The domain in this announcement is fictitious and has nothing to do with the real one. 13

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Vaccination flow 15

Slide 16

Slide 16 text

Modeling 16

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Example of conceptual model diagram 22

Slide 23

Slide 23 text

Implement and verify in code 23

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Verify with use cases 34

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Example of IO abstraction with interfaces https://speakerdeck.com/shin1x1/independent-core-layer-pattern-phpconsen2019 40

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

Model, code improvements 42

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

Conceptual model diagram Add inoculation status to the domain model. 44

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

Summary 50

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

I'm looking forward to your feedback! https://joind.in/talk/650b0 52

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

References Domain-Driven Design: Tackling Complexity in the Heart of Software https://www.amazon.com/gp/product/0321125215 Domain Modeling Made Functional: Tackle Software Complexity with Domain- Driven Design and F# https://pragprog.com/titles/swdddf/domain-modeling-made-functional/ Object oriented Model (Japanese) http://www.ics.kagoshima-u.ac.jp/edu/SoftwareEngineering/oo-model.html The story of visualizing an existing system through modeling (Japanese) https://speakerdeck.com/jnuank/moderingudeji-cun-sisutemufalseke-shi-hua- nilin-ndahua 56