To summarize the story that it was not enough to just use the same class just because they have the same value.
Let's write three slightly different examples. I changed the processing contents appropriately, but I made it the same configuration as the part where I struggled with the service I maintain.
Actually, the class design is decided based on the idea of DDD, but this time I will just say that it will be quite good just by devising the type.
Service and Table will be called domains for convenience.domains must not depend on Service or Table (do not draw an arrow from domains to anything other than domains)For example, suppose that a company mobile phone lent to an employee is broken and replaced. Receive the serial number you are currently using and replace it with the newly prepared serial number.
(SerialNumber) and (SerialNumber, SerialNumber) are prone to bugs.
Since the two serial numbers are distinguished only by the variable name, compilation can be done even with (changed, changed). ..
Then, perhaps by searching the data with the first argument, it feels like an execution exception such as "Such a serial number is not being lent."
(As an aside I just noticed, if I write it in the editor's complement, I may make a mistake because both changed and current are from c. It is unexpected that such a mistake is a long word)
Since domains has only one number class, the handling is simple, refactoring to the method of identifying all the parts that are working hard with variable names such as current and changed with the classes Current and Changed ..
It feels good.
Eventually it will go to the same column of the same table, so if you go to the layer as close to the database as possible, you may create SerialNumber from CurrentSerialNumber to increase the abstraction level.
However, SerialNumber should be prepared nearTable instead of domains and should beSerialNumber.from (CurrentSerialNumber) instead ofCurrentSerialNumber.toSerialNumber () .
If you generate it with to, domains depends on Table, and domains will allow you to use SerialNumber.
For example, suppose that there is a member management system that accepts member withdrawals. When withdrawing from the membership, set the status to have been withdrawn and fill in the blank withdrawal date to update the status.
It is difficult to be aware of the consistency of Status, and 0..1 is quite prone to execution exceptions.
I don't know what ** member I got from find (id), so I'm checking it.
After that, even when the member status is withdrawn and made permanent, if the setting is wrong, something that is not withdrawn will be made permanent.
If such a state update system is made into a different class for each state, a considerable amount of land mines can be eliminated.
Unsubscribed members can only be created from in-use members, in-use members do not always have a withdrawal date, and withdrawn members always have a withdrawal date.
You can trust that the result of find (id) is a in-use member. (Of course Table is responsible for checking instead)
Since Table.unSubscribe () is a method only for unsubscribing, it is no longer necessary to pass Status.UnSubscribed, and the system convenience setting value has disappeared from domains.
There are various situations such as application, change of contract form, unpaid fee, frozen, etc., and it is not only members but also various classes that can not go without management. If you try to use all the states properly with only the setting value in the same class, it will be so painful that the execution exception will die because it is covered with the contradiction check of the setting value and the ʻOptional` check.
Even if I use a typed language, I don't get the benefit of compilation first, so I feel like what language I'm writing ... ??
There are various designs here, and considering the event design, it will be too long, so I just introduced it. (Rather, I'm still studying event design ...)
For example, suppose that all user operations must be recorded as an operation history. If you make a purchase, save the purchase application history. If canceled, update the application history to canceled and create a cancellation operation history.
| operation | Application history | Cancellation history | 
|---|---|---|
| Apply | Can do | not exist | 
| Cancel | Updated to canceled | Can do | 
It's complicated, but it looks like this.
ʻApplication and Cancellation` are treated differently, so the classes are separated, but it is also convenient to handle it as an operation history for general purposes, so an example of creating a parent class.
Since it is complicated to do, there are many setting values, and since everything is saved as .save (), there is a possibility that the value and the number of times will be wrong.
In order to handle application and cancellation correctly in compilation while leaving the common class, it seems good to separate the parent class.
If you use a dedicated method in the same way as withdrawal, you can almost eliminate the set value.
By changing .save () to .cancel (applied, canceled), you can always update the two correctly.
Eventually the operation history falls to cancel or complete. After applying, you can start using it after confirming that it has arrived. The reason for creating a separate cancellation history is that it will be necessary to accept refunds from now on, and cancellation will be completed when the necessary processing is completed.
In terms of image, the final form looks like this.
| operation | Application history | Cancellation history | 
|---|---|---|
| Apply | Can do | not exist | 
| Reach | Complete | not exist | 
| Cancel | Updated to canceled | Can do | 
| Refund | Remains canceled | Complete | 
Basically, this is the form, but there are as many operation histories as there are services provided, such as updating member information and purchasing additional equipment.
It is extremely difficult to update these correctly every time, so I would like to receive the power of compilation while preparing some parent classes.
member as a parent of a used member and a withdrawn member, but it is better to quit., you will always have to repair ʻUser regardless of any specification changes, and you will end up retesting all specifications.XxxUser for each specification has a smaller range of influenceTable has data inconsistency and communication interruption, I will do exception check properlyService does what it should do based on the spec, so focus on behavior rather than checking for execution exceptionsSomething like that.
Recommended Posts