What is it? ~ 3 types of "no" ~

What is it?

I think it is necessary to consider a lot of "nothing" when writing code in practice, such as the value does not exist in a certain situation, or the result is not obtained even though the database is referenced. After all, there are many techniques for expressing things that don't exist, such as null, ʻOptional, and NullObject pattern`.

I would like to summarize what I think about how to handle them.

Tired of hearing?

Sure, I love ʻOptional and ʻEither, but this time I'm not talking about technique.

Recently, even if I say "no" in one word, I feel that there are some types. This time, I would like to summarize the types of "nothing".

What are the benefits?

It's just a "classification I've considered at this point", but it makes the scope of responsibility for each package clear. We also believe that it will be easier to get closer to a design that allows proper unit testing, and to organize and maintain logic.

Some design assumptions

Of course, there are many different designs, so I can't say anything, but here we assume the following outline. Should it be understood as Yurufuwa DDD style layer design? ... ??

name Overview Remarks
api It is the starting point of processing and uses service
It can be a URL or a command line tool
Will not appear this time
service Handle domain and realize the processing that the system should do
domain Describe business logic
repository Interface for interacting with databases and external systems Place as interface in domain
mapper Implement repository Not implemented this time

Let's try

As usual, we will prepare a convenient theme. This time, it seems that a certain telecommunications carrier manages contracts for fixed lines and mobile lines.

Outline of the theme

We will realize a system that manages contracts with certain members.

Members can have up to one mobile line and one fixed line. (Fixed line will not appear this time)

Mobile line has up to 1 voice option, The voice option comes with up to one answering machine option.

Based on the above data structure, the following requirements are fulfilled.

request

Achieve the following four requirements. Enter Member ID for all processing.

  1. Check if you can apply for a mobile line
  1. Check if you can cancel the answering machine option
  1. Get the items needed to cancel the answering machine option
  1. Refer to the total value of all contracted monthly usage fees

Blatantly "not" is studded ...

An example of a straightforward answer

domain First of all, simply express mobile line-> voice option-> answering machine option with ʻOptional. (This time, the technique of "nothing" does not have to be ʻOptional, but since it is the easiest, I will describe everything below with ʻOptional. The essence is almost the same even with null`.)

MobileLine.java


@AllArgsConstructor
public class MobileLine {
    @Getter
    private final MobileLineId id;
    @Getter
    private final MonthlyFee fee = new MonthlyFee(1500);
    @Getter
    private final Optional<VoiceOption> voiceOption;
}

VoiceOption.java


@AllArgsConstructor
public class VoiceOption {
    @Getter
    private final VoiceOptionId id;
    @Getter
    private final MonthlyFee fee = new MonthlyFee(700);
    @Getter
    private final Optional<AnswerPhoneOption> answerPhoneOption;
}

AnswerPhoneOption.java


@AllArgsConstructor
public class AnswerPhoneOption {
    @Getter
    private final AnswerPhoneOptionId id;
    @Getter
    private final MonthlyFee fee = new MonthlyFee(200);
}

It's simple. It has a price you might need and up to one sub-element. (MonthlyFee and XxxId are just primitive wrappers)

repository Next is repository. All you have to do is refer to the root class.

MobileLineRepository.java


public interface MobileLineRepository {
    Optional<MobileLine> findMobileLine(UserId userId);
}

Members may not have a mobile line, so it is ʻOptional`. Image of foreign key reference.

service Finally, there is service, but here we just connect them together.

MobileLineService.java


public class MobileLineService {
    private MobileLineRepository repository;

    // 1.Check if you can apply for a mobile line
    public boolean checkMobileLineApplicable(UserId userId) {
        Optional<MobileLine> mobileLineOptional = repository.findMobileLine(userId);

        return !mobileLineOptional.isPresent();
    }

    // 2.Check if you can cancel the answering machine option
    public boolean checkAnswerPhoneCancellable(UserId userId) {
        Optional<MobileLine> mobileLineOptional = repository.findMobileLine(userId);

        MobileLine mobileLine = mobileLineOptional
                .orElseThrow(() -> new RuntimeException("Mobile line not found"));

        if (mobileLine.getVoiceOption().isPresent()) {
            return mobileLine.getVoiceOption().get().getAnswerPhoneOption().isPresent();
        } else {
            return false;
        }
    }

    // 3.Get the items you need to cancel your voicemail option
    public AnswerPhoneOptionCancellation getAnswerPhoneOptionIdForCancellation(UserId userId) {
        Optional<MobileLine> mobileLineOptional = repository.findMobileLine(userId);

        MobileLine mobileLine = mobileLineOptional
                .orElseThrow(() -> new RuntimeException("Mobile line not found"));

        VoiceOption voiceOption = mobileLine.getVoiceOption()
                .orElseThrow(() -> new RuntimeException("Voice option not found"));

        AnswerPhoneOption answerPhoneOption = voiceOption.getAnswerPhoneOption()
                .orElseThrow(() -> new RuntimeException("Answering machine option not found"));

        return new AnswerPhoneOptionCancellation(
                mobileLine.getId(),
                voiceOption.getId(),
                answerPhoneOption.getId()
        );
    }

    // 4.Refer to the total value of all contracted monthly usage fees
    public MonthlyFee totalMonthlyFee(UserId userId) {
        Optional<MobileLine> mobileLineOptional = repository.findMobileLine(userId);

        MobileLine mobileLine = mobileLineOptional
                .orElseThrow(() -> new RuntimeException("Mobile line not found"));

        if (mobileLine.getVoiceOption().isPresent()) {
            if (mobileLine.getVoiceOption().get().getAnswerPhoneOption().isPresent()) {
                return MonthlyFee.sum(
                        mobileLine.getFee(),
                        mobileLine.getVoiceOption().get().getFee(),
                        mobileLine.getVoiceOption().get().getAnswerPhoneOption().get().getFee()
                );
            } else {
                return MonthlyFee.sum(
                        mobileLine.getFee(),
                        mobileLine.getVoiceOption().get().getFee()
                );
            }
        } else {
            return mobileLine.getFee();
        }
    }
}

You're done. (You can move the code to MobileLine a little more, but I've written it solidly in the service to make it easier to understand the whole picture of the process.)

I don't think there are many people who see this and feel "a wonderful code!" However, some people may not be able to figure out what is wrong and what to do.

So, from here, I would like to improve this code while interpreting the subject and explaining the code.

3 types of "no"

It's a bit of a leap, but I've been writing code for 1-2 years and I've been thinking a lot lately.

There are three types of "no"! That is. Let's think about each of them with the above theme and code.

A "No" is normal

The first is this.

In this example, the mobile line in "1. Check if you can apply for a mobile line" and The voice option and answering machine option of "4. Refer to the total value of all contracted monthly usage charges".

If these are not present, they will be treated as ** one pattern of normal system **. Basically this case is ** no exception **.

B "No" is possible but an error

This case corresponds to ** one error pattern ** if it fits the above phrase. However, if you say an error in one word, it is indistinguishable from the third type described later, so we will call it a ** business error ** in the sense of ** an error expected in the service specifications **.

It may be a service error. In short, I think it's good if there are no discrepancies in your relatives. I call it a business error because it is an error that should be detected by business logic.

This error writes logic to detect and guard. I can't say for sure because it may depend on the design, but there are no exceptions in this case. ** **

In this example, the answering machine option in "2. Check if you can cancel the answering machine option" is equivalent. If there is an answering machine option, it is "normal", and if it is not, it is "one of the many other reasons why you cannot cancel".

Difference between A and B

The difference is the difference in subsequent processing.

There is no difference in the flow because A is a normal system with or without it. On the other hand, if there is B, do XX, if not, do XX instead, or if not, skip to ~ ~, and so on.

B is an image of branching with yes / no in the flow diagram.

C "No" is impossible

The last is an error ** that is not assumed (defined) in the service specifications. I call it ** system error ** because this happens mostly for system reasons.

For example, the DB connection fails, the external system call times out, or the value that should have been corrupted in the DB cannot be referenced. There will be data corruption / loss due to double submission or erroneous operation.

In a nutshell, it is an error that does not occur if the service is operating normally and you use it as expected.

In this case ** with exceptions ** would be appropriate. I don't catch it one by one.

In this example, the mobile line of "2. Check if you can cancel the answering machine option" Mobile line and voice option and answering machine option in "3. Get the items required to cancel the answering machine option", The mobile line in "4. Refer to the total value of all contracted monthly usage charges" is equivalent.

For example, in 2., the mobile line is (thematically) a certain premise, so it is unexpected that it is not there anymore. I don't know what happened. (A certain premise is, for example, a process called from a screen that can only be transitioned to a person with a mobile line. It is not unusual, isn't it?)

Difference between B and C

Unlike B, C does not appear in the flow chart with yes / no.

For example, "yes / no with status inconsistency" or "yes / no with database connection failure" is not specified in the service specification flow diagram, right?

Also, unlike B, C is often unable to perform subsequent processing. Doesn't it make sense to check for voice options when you fail to browse your mobile line? In many cases, if it does occur, nothing will happen.

In other cases, B is reproduced but C is not.

B says, for example, "If you don't have the answering machine option, you can check how many times you can cancel ** and you will get the same result **". C may have something like "I was attacked and failed to connect to the database due to heavy load, but I dealt with it ** and succeeded when I reapplied **".

Then what should i do

Even if I simply say "presence or absence of voice option", I found that the handling is different at that time.

So, this time, I would like to take the plunge and take the policy of "making a different class each time."

Organize 4 requirements

I will try to organize the necessary information as "No".

1. Check if you can apply for a mobile line

It doesn't do any calculations, so it's enough to know if it really exists.

2. Check if you can cancel the answering machine option

There is no particular calculation here either, so there seems to be no required value.

3. Get the items needed to cancel the answering machine option

Assuming that everything is here, we need ʻID` for all elements.

4. Refer to the total value of all contracted monthly usage fees

Here it is normal even if it is not below the mobile line, and in some cases the price of the element is required.

Compare with the first class

In this way, the MobileLine that I made first actually looks like a union of four requirements.

MobileLine.java


@AllArgsConstructor
public class MobileLine {
    @Getter
    private final MobileLineId id;
    @Getter
    private final MonthlyFee fee = new MonthlyFee(1500);
    @Getter
    private final Optional<VoiceOption> voiceOption;
}

However, it turns out that ʻID and monthly charges are less likely to be required, and that VoiceOption is not always ʻOptional.

Certainly, if you have all the elements and set it to ʻOptional` in consideration of the possibility that it does not exist, you can meet all the requirements. However, in reality, this doesn't have much merit, and it just increases in spiciness. (I will touch on that at the end)

Therefore, from here, I will introduce "a class that focuses on the minimum elements to the last minute".

Renewed class!

The number of classes will be very large, but I decided to take the plunge, so I will create a large number of classes.

1. Check if you can apply for a mobile line

No class required. The presence or absence of boolean is sufficient.

2. Check if you can cancel the answering machine option

MobileLineForAnswerPhoneCancellableCheck.java


@AllArgsConstructor
public class MobileLineForAnswerPhoneCancellableCheck {
    @Getter
    private final Optional<VoiceOptionForAnswerPhoneCancellableCheck> voiceOption;
}

VoiceOptionForAnswerPhoneCancellableCheck.java


@AllArgsConstructor
public class VoiceOptionForAnswerPhoneCancellableCheck {
    @Getter
    private final Optional<AnswerPhoneOptionForAnswerPhoneCancellableCheck> answerPhoneOption;
}

AnswerPhoneOptionForAnswerPhoneCancellableCheck.java


@AllArgsConstructor
public class AnswerPhoneOptionForAnswerPhoneCancellableCheck {
}

With the current specifications, it is possible to judge whether or not to apply based on the presence or absence, so there is no value at all. (It feels a little useless, but in reality, various statuses, contract continuation years, and various other values will be required, and I hope that such delusions will complement your brain.)

3. Get the items needed to cancel the answering machine option

MobileLineForAnswerPhoneCancellation.java


@AllArgsConstructor
public class MobileLineForAnswerPhoneCancellation {
    @Getter
    private final MobileLineId id;
    @Getter
    private final VoiceOptionForAnswerPhoneCancellation voiceOption;
}

VoiceOptionForAnswerPhoneCancellation.java


@AllArgsConstructor
public class VoiceOptionForAnswerPhoneCancellation {
    @Getter
    private final VoiceOptionId id;
    @Getter
    private final AnswerPhoneOptionForAnswerPhoneCancellation answerPhoneOption;
}

AnswerPhoneOptionForAnswerPhoneCancellation.java


@AllArgsConstructor
public class AnswerPhoneOptionForAnswerPhoneCancellation {
    @Getter
    private final AnswerPhoneOptionId id;
}

This has a different atmosphere than before. I have a ʻID that is only needed for this requirement, and all ʻOptionals are missing.

4. Refer to the total value of all contracted monthly usage fees

MobileLineForTotalMonthlyFee.java


@AllArgsConstructor
public class MobileLineForTotalMonthlyFee {
    @Getter
    private final MonthlyFee fee = new MonthlyFee(1500);
    @Getter
    private final Optional<VoiceOptionForTotalMonthlyFee> voiceOption;
}

VoiceOptionForTotalMonthlyFee.java


@AllArgsConstructor
public class VoiceOptionForTotalMonthlyFee {
    @Getter
    private final MonthlyFee fee = new MonthlyFee(700);
    @Getter
    private final Optional<AnswerPhoneOptionForTotalMonthlyFee> answerPhoneOption;
}

AnswerPhoneOptionForTotalMonthlyFee.java


@AllArgsConstructor
public class AnswerPhoneOptionForTotalMonthlyFee {
    @Getter
    private final MonthlyFee fee = new MonthlyFee(200);
}

It also has a monthly fee that is only needed for this requirement and has a subordinate element in ʻOptional`.

Repository

Since the returned class has changed, so does the repository method.

MobileLineRepository.java


public interface MobileLineRepository {
    boolean isMobileLineApplicable(UserId userId); //No need to get the class back

    MobileLineForAnswerPhoneCancellableCheck findForCancellable(UserId userId);

    MobileLineForAnswerPhoneCancellation findForCancel(UserId userId);

    MobileLineForTotalMonthlyFee findForTotalMonthlyFee(UserId userId);
}

ʻOptional` is gone!

Where did ʻOptional disappear here, for example, from MobileLineForAnswerPhoneCancellation.java`? Instead of moving somewhere instead, it's really gone.

In the implementation class of Repository, if a state inconsistency occurs, it will be an exception. The part that was doing .orElseThrow () in the service layer is checked for inconsistency before being wrapped in ʻOptional`, and if it occurs, it will be an exception.

You must be able to get () later anyway, so let's make an exception when you find that you can't. That way you don't have to keep it in domain as ʻOptional` and check it later.

service

It's easy if you have all this. I even think that the easiest one is service in the first place.

You just use the domain that you've worked so hard on.

MobileLineService.java


public class MobileLineService {
    private MobileLineRepository repository;

    // 1.Check if you can apply for a mobile line
    public boolean checkMobileLineApplicable(UserId userId) {
        return repository.isMobileLineApplicable(userId);
    }

    // 2.Check if you can cancel the answering machine option
    public boolean checkAnswerPhoneCancellable(UserId userId) {
        MobileLineForAnswerPhoneCancellableCheck mobileLine = repository.findForCancellable(userId);

        return mobileLine.getVoiceOption().map(it -> it.getAnswerPhoneOption().isPresent()).orElse(false); //A little ingenuity with map or Else
    }

    // 3.Get the items you need to cancel your voicemail option
    public AnswerPhoneOptionCancellation cancelAnswerPhoneOption(UserId userId) {
        MobileLineForAnswerPhoneCancellation mobileLine = repository.findForCancel(userId);

        return new AnswerPhoneOptionCancellation(
                mobileLine.getId(),                                    //No need to check isPresent
                mobileLine.getVoiceOption().getId(),                   //The value is confirmed because the repository did not raise an exception
                mobileLine.getVoiceOption().getAnswerPhoneOption().getId()
        );
    }

    // 4.Refer to the total value of all contracted monthly usage fees
    public MonthlyFee totalMonthlyFee(UserId userId) {
        MobileLineForTotalMonthlyFee mobileLine = repository.findForTotalMonthlyFee(userId);

        return MonthlyFee.sum(
                mobileLine.getFee(),                                                          //A little ingenuity with map or Else
                mobileLine.getVoiceOption().map(it -> it.getFee()).orElse(MonthlyFee.zero()), //It may be a little easier if you add it obediently as 0 yen
                mobileLine.getVoiceOption().flatMap(it -> it.getAnswerPhoneOption()).map(it -> it.getFee()).orElse(MonthlyFee.zero())
        );
    }
}

The big difference from the first MobileLineService is that it" does not check for system errors ". "There shouldn't be an element" believes in the class returned by repository.

So you can focus only on the calculations that have to be done for your requirements.

Actually, just a little more ...

Now that I've come this far, there's one more thing I want to fix.

So you can focus only on the calculations that must be done for your requirements.

This is the business logic. You might think of it as "the necessary calculations to meet the service specifications".

You want to test the calculation, right? You should want to. It's impossible not to do it, right? Yes, I want to.

Here or so!

return mobileLine.getVoiceOption().map(it -> it.getAnswerPhoneOption().isPresent()).orElse(false);

Here or so!

return MonthlyFee.sum(
        mobileLine.getFee(),
        mobileLine.getVoiceOption().map(it -> it.getFee()).orElse(MonthlyFee.zero()),
        mobileLine.getVoiceOption().flatMap(it -> it.getAnswerPhoneOption()).map(it -> it.getFee()).orElse(MonthlyFee.zero())
);

Are you confident in deploying without checking the operation? Is it absolutely okay?

If you want to pass all the patterns in the service test, you have to prepare a lot of repository return values.

It's mocking or registering dummy data in the database, but you can't do that.

Therefore, we should pay attention to the following description in the table of the first layer description.

domain Describe business logic

Also renewed class! !!

I will post it only once. This is the final improvement.

2. Check if you can cancel the answering machine option

MobileLineForAnswerPhoneCancellableCheck.java


@AllArgsConstructor
public class MobileLineForAnswerPhoneCancellableCheck {
    private final Optional<VoiceOptionForAnswerPhoneCancellableCheck> voiceOption;

    public boolean isAnswerPhoneCancellable() {
        return voiceOption.map(it -> it.isAnswerPhoneCancellable()).orElse(false);
    }
}

VoiceOptionForAnswerPhoneCancellableCheck.java


@AllArgsConstructor
public class VoiceOptionForAnswerPhoneCancellableCheck {
    private final Optional<AnswerPhoneOptionForAnswerPhoneCancellableCheck> answerPhoneOption;

    public boolean isAnswerPhoneCancellable() {
        return answerPhoneOption.isPresent();
    }
}

AnswerPhoneOptionForAnswerPhoneCancellableCheck.java


public class AnswerPhoneOptionForAnswerPhoneCancellableCheck {
}

Ask the root class "Can you apply?"

3. Get the items needed to cancel the answering machine option

MobileLineForAnswerPhoneCancellation.java


@AllArgsConstructor
public class MobileLineForAnswerPhoneCancellation {
    private final MobileLineId mobileLineId;
    private final VoiceOptionId voiceOptionId;
    private final AnswerPhoneOptionId answerPhoneOptionId;

    public AnswerPhoneOptionCancellation cancel() {
        return new AnswerPhoneOptionCancellation(
                mobileLineId,
                voiceOptionId,
                answerPhoneOptionId
        );
    }
}

I took the plunge and decided to stop the nesting structure and make it a flat class. I need them all anyway.

I just said "a collection of materials for information necessary for cancellation". If you tell the root class "Create the information necessary for cancellation", it will be assembled using the materials. You can't see what the material is specifically on the outside. It's encapsulated.

4. Refer to the total value of all contracted monthly usage fees

MobileLineForTotalMonthlyFee.java


@AllArgsConstructor
public class MobileLineForTotalMonthlyFee {
    private final MonthlyFee fee = new MonthlyFee(1500);
    private final Optional<VoiceOptionForTotalMonthlyFee> voiceOption;

    public MonthlyFee getTotalMonthlyFee() {
        return MonthlyFee.sum(
                fee,
                voiceOption.map(it -> it.voiceOptionAndAnswerPhoneOptionFee()).orElse(MonthlyFee.zero())
        );
    }
}

VoiceOptionForTotalMonthlyFee.java


@AllArgsConstructor
public class VoiceOptionForTotalMonthlyFee {
    private final MonthlyFee fee = new MonthlyFee(700);
    private final Optional<AnswerPhoneOptionForTotalMonthlyFee> answerPhoneOption;

    public MonthlyFee voiceOptionAndAnswerPhoneOptionFee() {
        return MonthlyFee.sum(
                fee,
                answerPhoneOption.map(it -> it.answerPhoneOptionFee()).orElse(MonthlyFee.zero())
        );
    }
}

AnswerPhoneOptionForTotalMonthlyFee.java


@AllArgsConstructor
public class AnswerPhoneOptionForTotalMonthlyFee {
    private final MonthlyFee fee = new MonthlyFee(200);

    public MonthlyFee answerPhoneOptionFee() {
        return fee;
    }
}

I was a little confused here, but prepare methods that return the sum of the prices below themselves, and the upper class will add to itself. Well, the details inside are trivial, and here too, all you have to do is say "summ up" to the root class.

Repository

I think it's better to divide the repository according to the requirements of the class to be returned. It happened when I proceeded with package organization. (Recently, I'm still not very confident.)

Therefore, the repository is also divided into as many classes as the number of created classes.

I will omit the code.

service

MobileLineService.java


public class MobileLineService {
    private MobileLineRepository mobileLineRepository;
    private MobileLineForAnswerPhoneCancellableCheckRepository forAnswerPhoneCancellableCheckRepository;
    private MobileLineForAnswerPhoneCancellationRepository forAnswerPhoneCancellationRepository;
    private MobileLineForTotalMonthlyFeeRepository totalMonthlyFeeRepository;

    // 1.Check if you can apply for a mobile line
    public boolean checkMobileLineApplicable(UserId userId) {
        return mobileLineRepository
                .isMobileLineApplicable(userId);
    }

    // 2.Check if you can cancel the answering machine option
    public boolean checkAnswerPhoneCancellable(UserId userId) {
        return forAnswerPhoneCancellableCheckRepository.find(userId)
                .isAnswerPhoneCancellable();
    }

    // 3.Get the items you need to cancel your voicemail option
    public AnswerPhoneOptionCancellation cancelAnswerPhoneOption(UserId userId) {
        return forAnswerPhoneCancellationRepository.find(userId)
                .cancel();
    }

    // 4.Refer to the total value of all contracted monthly usage fees
    public MonthlyFee totalMonthlyFee(UserId userId) {
        return totalMonthlyFeeRepository.find(userId)
                .getTotalMonthlyFee();
    }
}

Now you just request the root class from the repository and request the root class to do the math! !!

That's the only service.

You have separated the calculation and processing.

The calculation test that moved to the domain class can be done manually without inputting dummy data in the database. You can new directly under the root class and test as many as you like.

Consideration of improvement

Responsibilities

I posted the code three times.

Looking at it like this, you can see that the responsibility was gradually separated from the service and moved to the domain. Or rather, the first service, after all, was too hard.

Robustness

Let's look at it from a slightly different perspective

As a general rule, ʻOptional should not do get () . (I will omit the details, but basically it is better to look for some improvement plan with the runner-up after get ()`.)

As we improved, the get () and ʻorElseThrow () disappeared, so there were no exceptions around ʻOptional.

Also, by taking the form of commanding the root class, @ Getter has disappeared. This means that the encapsulation was successful.

The last class created is separated by requirement, so even if an additional value is required only for a certain requirement, the modification will affect only that class. The service class just says "~~" to the root class without knowing the elements inside.

Even if the application date is added to the cancellation judgment, there is no need to repair the service, and recombining evaluation is unnecessary just in case of price calculation.

3 types of "no"

normal

There are no exceptions

Not specified as a branch in the service specification flow diagram 1.png

Business error

There are no exceptions

Specified as a branch in the service specification flow diagram 2.png

Subsequent processing after occurrence is also processed normally according to the specifications.

Since it is written in the service specification, it is detected by domain which writes business logic

If the data status is the same, the same business error will always occur

System error

Use exceptions

Not specified as a branch in the service specification flow diagram 3.png

In many cases, subsequent processing cannot be continued after it occurs.

Since it is not written in the service specification and involves exceptions, it is guarded by mapper instead of detecting by domain which writes business logic.

Even if the data status is the same, it may or may not occur due to load or communication interruption. (If the data is inconsistent, it will always occur in the same way)

Union class vs specialized class

First of all, in the case of specialized classes, modifying one requirement does not affect another.

The union class sets ʻOptionalto all values that may only be held for certain requirements. (OrList`)

This is, for example, "The cancellation application date is ʻempty except when canceling, but when canceling cancellation, there should be a value on the cancellation application date." ʻOptional that requires a large amount of prerequisite knowledge such as" It is always an empty list immediately after application, but when canceling it should be a list containing 1-3 elements "is created. (Or List) This is super spicy. (true story)

Considering application, change, cancellation, cancellation of them, optional items, etc., the union class is covered with ʻOptional. ʻOr Else Throw ("There should be ~ ~") Can't you easily imagine being covered?

exception

I don't think exceptions in domain should be made.

Since it is a calculation to do with domain, state inconsistency is out of the question, This is because I think that the error should be expressed by the value because the error is expected as long as it is calculated as business logic.

Let's push all the exceptions to mapper. (Slightly violent)

What about service? service may be appropriate for catch. For example, "If there is a system error, an alarm will be issued."

What remains in the service

This time there was only one route class, but for example Like "I have a contract for an answering machine option" & "No unpaid amount for an answering machine option" & "Two years have passed since the contract" Other repositories and root classes will also appear if it is a compound condition.

I think it is the responsibility of the service layer to handle them and to handle large domains that require all of those domains. now.

It's like a class that doesn't know most of the specific calculations and system errors that the end should consider, but just knows the "processing order" and the "whole characters".

test

In the first example, it was necessary to input dummy data in the database in order to cover the essential calculation logic patterns.

In the last example, the calculation is separated into domain, so you can new and test it with any value you like.

Comprehensive

I wrote at the beginning:

It's just a "classification I've considered at this point", but it clarifies the scope of responsibility for each package. We also believe that it will be easier to get closer to a design that allows proper unit testing, and to organize and maintain logic.

Actually, I didn't think much at this point, so I thought I'd erase it if I couldn't connect well, but I was relieved that it was unexpectedly connected.

System errors, processing and calculations are now in each layer, can be unit tested, and requirement changes no longer affect other requirements!

that's all

that's all.

Could you introduce that there are quite a lot of merits just by considering "nothing"?

ʻOptional is convenient, and above all, I love the failure type Soyu, but I don't make anything unreasonably ʻOptional I think that when compared with the demands, it will naturally come closer to a good design.

Recommended Posts

What is it? ~ 3 types of "no" ~
What is docker run -it?
'% 02d' What is the percentage of% 2?
What kind of StringUtil is good
What kind of method is define_method?
What is testing? ・ About the importance of testing
What is Docker? What purpose is it used for?
What is the data structure of ActionText?
What is Cubby
What is Docker?
What is java
What is maven?
What is Jackson?
What is Docker
What is self
What is Jenkins
What is ArgumentMatcher?
What is IM-Juggling?
What is params
What is SLF4J?
What is Facade? ??
What is Java <>?
What is Gradle?
What is POJO
What is Java
What is centOS
What is RubyGem?
What is programming?
Is it OutOfMemoryError?
What is before_action?
What is Docker
What is Byte?
What is Tomcat
What is JSP? ~ Let's know the basics of JSP !! ~
No enclosing instance of type Hoge is accessible.
Retrieve the first day of week in current locale (what day of the week is it today?)
What is Maven Assembly?
Existence check of has_many and belongs_to-optional: What is true?
What is `docker-compose up`?
What is a constructor?
What is vue cli
What is an interface?
What is Ruby's self?
What is hard coding?
What is a stream
What is Ruby's attr_accessor?
What is permission denied?
What is instance control?
What is an initializer?
What is Spring Tools 4
What is an operator?
What is object orientation?
What is MVC model?
What is an annotation?
What is Java technology?
What is Java API-java
What is @ (instance variable)?
What is Gradle's Artifact?
What is JPA Auditing?
[Swift] What is dismiss?
[Java] What is flatMap?