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