It feels good to prepare different classes for the same table values

Introduction

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.

Article premise

Case 1 Handle two primary keys

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.

Classes and implementations

number_1.png (SerialNumber) and (SerialNumber, SerialNumber) are prone to bugs.

number_3.png 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)

Refactor

number_2.png 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 ..

number_4.png It feels good.

actually

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.

Case 2 Update the status of one class

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.

Classes and implementations

user_1.png It is difficult to be aware of the consistency of Status, and 0..1 is quite prone to execution exceptions.

user_3.png 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.

Refactor

user_2.png 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.

user_4.png 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.

actually

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 ...)

Case 3 Have a common parent class

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.

Classes and implementations

operation_1.png ʻ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.

operation_3.png 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.

Refactor

operation_2.png In order to handle application and cancellation correctly in compilation while leaving the common class, it seems good to separate the parent class.

operation_4.png 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.

actually

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.

Summary

Basic policy

A sign that seems to be dangerous

Coping

What to do as a basic stance

Something like that.

Recommended Posts

It feels good to prepare different classes for the same table values
[Ruby] Setting values ​​and memorandum for the table
What Java engineers need to prepare for the Java 11 release
I want to return multiple return values for the input argument
"Wait for the process to finish." And kill the process because it remains.