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
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
after prior appointment. Vaccination is currently given only once. Establish a system for making vaccination reservations and registering vaccinees. 14
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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