[JAVA] Qu'Est-ce que c'est? ~ 3 types de "non" ~

Qu'Est-ce que c'est?

Je pense qu'il est nécessaire de considérer beaucoup de "rien" lors de l'écriture du code dans la pratique, comme la valeur n'existe pas dans une certaine situation, ou le résultat n'est pas obtenu même si la base de données est référencée. Après tout, il existe de nombreuses techniques pour exprimer des choses qui n'existent pas, telles que «null», «facultatif» et «modèle NullObject».

Je voudrais résumer ce que je pense de la façon de les gérer.

Fatigué d'entendre?

Bien sûr, j'adore «Optionnel» et «Soit», mais cette fois je ne parle pas de techniques.

Récemment, même si je dis «non» en un mot, j'ai l'impression qu'il y en a des types. Cette fois, je voudrais résumer les types de «rien».

Quels sont les bénéfices?

C'est juste une «classification que j'ai envisagée à ce stade», mais cela rend clairement la portée de la responsabilité de chaque paquet. Nous pensons également qu'il sera plus facile de se rapprocher d'une conception permettant des tests unitaires appropriés, et d'organiser et de maintenir la logique.

Quelques hypothèses de conception

Bien sûr, il existe de nombreux modèles différents, donc je ne peux rien dire, mais ici nous supposons le contour suivant. Doit-il être compris comme «conception de couches de style Yurufuwa DDD?» ... ??

Nom Aperçu Remarques
api C'est le point de départ du traitement et utilise le service
Cela peut être une URL ou un outil de ligne de commande
N'apparaîtra pas cette fois
service Gérer le domaine et réaliser le traitement que le système doit effectuer
domain Décrire la logique métier
repository Interface pour interagir avec des bases de données et des systèmes externes Placer comme interface dans le domaine
mapper Mettre en œuvre le référentiel Non mis en œuvre cette fois

Essayons

Comme d'habitude, nous préparerons un thème pratique. Cette fois, il semble qu'un certain opérateur de télécommunications gère les contrats de lignes fixes et mobiles.

Aperçu du thème

Nous réaliserons un système de gestion des contrats avec certains membres.

Les membres peuvent avoir jusqu'à une ligne mobile et une ligne fixe. (La ligne fixe n'apparaîtra pas cette fois)

La ligne mobile a jusqu'à 1 option vocale, L'option vocale comprend jusqu'à une option de répondeur.

Sur la base de la structure de données ci-dessus, les exigences suivantes sont remplies.

demande

Répondez aux quatre exigences suivantes. Entrez l'ID de membre pour tous les traitements.

  1. Vérifiez si vous pouvez demander une ligne mobile
  1. Vérifiez si vous pouvez annuler l'option de répondeur
  1. Obtenez les éléments nécessaires pour annuler l'option de répondeur
  1. Reportez-vous à la valeur totale de tous les frais d'utilisation mensuels contractuels

De manière flagrante, "non" est clouté ...

Un exemple de réponse simple

domain Tout d'abord, exprimez simplement ligne mobile-> option vocale-> option répondeur avec ʻOptional`. (Cette fois, la technique du «rien» ne doit pas être «facultative», mais comme c'est la plus simple, je décrirai tout ci-dessous avec «facultatif». L'essence est presque la même même avec «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);
}

C'est simple. Il a un prix dont vous pourriez avoir besoin et jusqu'à un sous-élément. (MonthlyFee et XxxId ne sont que des wrappers primitifs)

repository Vient ensuite repository. Cela dit, il ne vous reste plus qu'à vous référer à la classe racine.

MobileLineRepository.java


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

Les membres peuvent ne pas avoir de ligne mobile, c'est donc «en option». Image de référence de clé externe.

service Enfin, il y a service, mais ici, nous nous connectons simplement.

MobileLineService.java


public class MobileLineService {
    private MobileLineRepository repository;

    // 1.Vérifiez si vous pouvez demander une ligne mobile
    public boolean checkMobileLineApplicable(UserId userId) {
        Optional<MobileLine> mobileLineOptional = repository.findMobileLine(userId);

        return !mobileLineOptional.isPresent();
    }

    // 2.Vérifiez si vous pouvez annuler l'option de répondeur
    public boolean checkAnswerPhoneCancellable(UserId userId) {
        Optional<MobileLine> mobileLineOptional = repository.findMobileLine(userId);

        MobileLine mobileLine = mobileLineOptional
                .orElseThrow(() -> new RuntimeException("Ligne mobile introuvable"));

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

    // 3.Obtenez les éléments nécessaires pour annuler l'option Répondeur
    public AnswerPhoneOptionCancellation getAnswerPhoneOptionIdForCancellation(UserId userId) {
        Optional<MobileLine> mobileLineOptional = repository.findMobileLine(userId);

        MobileLine mobileLine = mobileLineOptional
                .orElseThrow(() -> new RuntimeException("Ligne mobile introuvable"));

        VoiceOption voiceOption = mobileLine.getVoiceOption()
                .orElseThrow(() -> new RuntimeException("Option vocale introuvable"));

        AnswerPhoneOption answerPhoneOption = voiceOption.getAnswerPhoneOption()
                .orElseThrow(() -> new RuntimeException("Option de répondeur introuvable"));

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

    // 4.Se référer à la valeur totale de tous les frais d'utilisation mensuels contractuels
    public MonthlyFee totalMonthlyFee(UserId userId) {
        Optional<MobileLine> mobileLineOptional = repository.findMobileLine(userId);

        MobileLine mobileLine = mobileLineOptional
                .orElseThrow(() -> new RuntimeException("Ligne mobile introuvable"));

        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();
        }
    }
}

Vous avez terminé. (Vous pouvez déplacer un peu plus le code vers MobileLine, mais je l'ai écrit dans le service pour faciliter la compréhension de l'ensemble du processus.)

Je ne pense pas qu'il y ait beaucoup de gens qui voient cela et ressentent un "code merveilleux!" Cependant, certaines personnes peuvent ne pas être en mesure de comprendre ce qui ne va pas et ce qu'il faut faire.

Donc, à partir d'ici, je voudrais améliorer ce code tout en interprétant le sujet et en expliquant le code.

3 types de "non"

C'est un peu un saut, mais j'écris du code depuis 1 à 2 ans et je me sens fortement ces derniers temps.

Il existe trois types de «non»! C'est. Pensons à chacun avec le thème et le code ci-dessus.

Un «non» est normal

Le premier est celui-ci.

Dans cet exemple, la ligne mobile dans "1. Vérifiez si vous pouvez demander une ligne mobile" et L'option vocale et l'option de répondeur téléphonique de «4. Reportez-vous à la valeur totale de tous les frais d'utilisation mensuels contractuels».

S'ils ne sont pas présents, ils seront traités comme ** un modèle de système normal **. Fondamentalement, ce cas n'est ** aucune exception **.

B "Non" est possible mais une erreur

Ce cas correspond à ** un modèle d'erreur ** s'il correspond à la phrase ci-dessus. Cependant, si vous prononcez une erreur dans un mot, elle ne peut pas être distinguée du troisième type décrit plus loin, nous l'appellerons donc une ** erreur commerciale ** dans le sens de ** une erreur attendue dans les spécifications du service **.

Cela peut être une erreur de service. Bref, je pense que c'est bien s'il n'y a pas de divergences dans vos proches. J'appelle cela une erreur métier car c'est une erreur qui doit être détectée par la logique métier.

Cette erreur écrit la logique pour détecter et protéger. Je ne peux pas le dire avec certitude car cela peut dépendre de la conception, mais il n'y a pas d'exceptions dans ce cas. ** **

Dans cet exemple, l'option de répondeur "2. Vérifiez si vous pouvez annuler l'option de répondeur" est équivalente. S'il existe une option de répondeur, c'est «normal», et si ce n'est pas le cas, c'est «l'une des nombreuses autres raisons pour lesquelles vous ne pouvez pas annuler».

Différence entre A et B

La différence est la différence dans le traitement ultérieur.

Puisque A est un système normal avec ou sans lui, il n'y a aucune différence dans le flux. D'autre part, s'il y a B, faites XX, sinon, faites XX à la place, ou sinon, passez à ~ ~, et ainsi de suite.

B est une image de branchement avec «oui / non» dans le diagramme de flux.

C "Non" est impossible

La dernière est ** une erreur ** qui n'est pas supposée (définie) dans les spécifications du service. Je l'appelle ** erreur système ** car cela se produit principalement pour des raisons système.

Par exemple, la connexion à la base de données échoue, l'appel du système externe expire ou la valeur qui aurait dû être corrompue dans la base de données ne peut pas être référencée. Il y aura corruption / perte de données en raison d'une double soumission ou d'une opération erronée.

En un mot, c'est une erreur qui ne se produit pas si le service fonctionne normalement et qu'il est utilisé comme prévu.

Dans ce cas ** avec des exceptions ** serait approprié. Je ne l'attrape pas un par un.

Dans cet exemple, la ligne mobile de "2. Vérifiez si vous pouvez annuler l'option de répondeur" Options de ligne mobile et de voix et options de répondeur dans "3. Obtenez les éléments nécessaires pour annuler l'option de répondeur", La ligne mobile dans "4. Se référer à la valeur totale de tous les frais d'utilisation mensuels contractés" est équivalente.

Par exemple, en 2., il y a une ligne mobile (par thème), il est donc inattendu qu'il n'y en ait pas. Je ne sais pas ce qui s'est passé. (Une certaine prémisse est, par exemple, un processus appelé à partir d'un écran qui ne peut être transféré qu'à une personne avec une ligne mobile. Ce n'est pas inhabituel, n'est-ce pas?)

Différence entre B et C

Contrairement à B, C n'apparaît pas dans l'organigramme avec «oui / non».

Par exemple, «une incohérence d'état oui / non s'est produite» ou «échec de connexion oui / non à la base de données» n'est pas spécifié dans le diagramme de flux de spécifications de service, n'est-ce pas?

De plus, contrairement à B, C est souvent incapable d'effectuer un traitement ultérieur. N'est-il pas judicieux de vérifier les options vocales lorsque vous ne parvenez pas à parcourir votre ligne mobile? Dans de nombreux cas, si cela se produit, rien ne se passera.

Dans d'autres cas, B est reproduit mais pas C.

B dit, par exemple, "Si vous n'avez pas l'option de répondeur, vous pouvez vérifier combien de fois vous pouvez annuler ** et vous obtiendrez le même résultat **". C peut avoir quelque chose comme "J'ai été attaqué et je n'ai pas réussi à me connecter à la base de données sous une charge importante, mais je l'ai traité ** et j'ai réussi ** lorsque j'ai réappliqué."

Alors que dois-je faire

Même si je dis simplement "présence ou absence d'option vocale", j'ai trouvé que la prise en main est différente à ce moment-là.

Donc, cette fois, j'aimerais franchir le pas et adopter la politique de «créer une classe différente à chaque fois».

Organiser 4 exigences

J'essaierai d'organiser les informations nécessaires comme "Non".

1. Vérifiez si vous pouvez demander une ligne mobile

Il ne fait aucun calcul, il suffit donc de savoir s'il existe vraiment.

2. Vérifiez si vous pouvez annuler l'option de répondeur

Il n'y a pas non plus de calcul particulier ici, donc il ne semble pas y avoir de valeur requise.

3. Obtenez les éléments nécessaires pour annuler l'option de répondeur

En supposant que tout est ici, nous avons besoin de «ID» pour tous les éléments.

4. Reportez-vous à la valeur totale de tous les frais d'utilisation mensuels contractuels

Ici, c'est normal même s'il n'est pas en dessous de la ligne mobile, et dans certains cas, le prix de l'élément est requis.

Comparez avec la première classe

En regardant les choses de cette manière, le premier «MobileLine» que j'ai créé est en fait une somme de quatre exigences.

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;
}

Cependant, il s'avère que «ID» et les frais mensuels sont moins susceptibles d'être requis, et que «VoiceOption» n'est pas toujours «facultative».

Certes, si vous avez tous les éléments et que vous le définissez sur «Facultatif» en tenant compte de la possibilité qu'il n'existe pas, vous pouvez répondre à toutes les exigences. Cependant, en réalité, cela n'a pas beaucoup de mérite, et cela ne fait qu'augmenter le piquant. (Je vais en parler à la fin)

Par conséquent, à partir de là, j'introduirai "une classe qui se concentre sur les éléments minimaux jusqu'à la dernière minute".

Classe renouvelée!

Le nombre de classes sera très important, mais j'ai décidé de franchir le pas, donc je vais créer un grand nombre de classes.

1. Vérifiez si vous pouvez demander une ligne mobile

Aucun cours requis. La présence ou l'absence de «booléen» est suffisante.

2. Vérifiez si vous pouvez annuler l'option de répondeur

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 {
}

Avec les spécifications actuelles, il est possible de juger s'il faut appliquer ou non en fonction de la présence ou de l'absence, donc il n'y a aucune valeur du tout. (Cela semble un peu inutile, mais en réalité, vous aurez besoin de différentes valeurs telles que différents statuts et des années de continuation de contrat, et j'espère que vous pourrez compléter votre cerveau avec de telles illusions.)

3. Obtenez les éléments nécessaires pour annuler l'option de répondeur

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;
}

Cela a une atmosphère différente qu'avant. J'ai un «ID» qui n'est nécessaire que pour cette exigence, et tous les «optionnels» sont manquants.

4. Reportez-vous à la valeur totale de tous les frais d'utilisation mensuels contractuels

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);
}

Il a également une redevance mensuelle qui n'est nécessaire que pour cette exigence et a un élément subordonné dans «Facultatif».

Dépôt

Puisque la classe retournée a changé, la méthode dans le référentiel change également.

MobileLineRepository.java


public interface MobileLineRepository {
    boolean isMobileLineApplicable(UserId userId); //Pas besoin de récupérer la classe

    MobileLineForAnswerPhoneCancellableCheck findForCancellable(UserId userId);

    MobileLineForAnswerPhoneCancellation findForCancel(UserId userId);

    MobileLineForTotalMonthlyFee findForTotalMonthlyFee(UserId userId);
}

«Optionnel» est parti!

Où «Optionnel» a-t-il disparu ici, par exemple de «MobileLineForAnswerPhoneCancellation.java»? Au lieu de déménager quelque part, c'est vraiment parti.

Dans la classe d'implémentation de Repository, si une incohérence d'état se produit, ce sera une exception. La partie qui faisait .orElseThrow () dans la couche de service est vérifiée pour incohérence avant d'être enveloppée dans ʻOptional`, et si cela se produit, ce sera une exception.

De toute façon, vous devez pouvoir get () plus tard, alors faisons une exception lorsque vous constatez que vous ne pouvez pas. De cette façon, vous n'avez pas à le conserver dans domain comme ʻOptional` et à le vérifier ultérieurement.

un service

C'est facile si vous avez tout cela. Je pense même que le plus simple est le «service» en premier lieu.

Tout ce que vous avez à faire est d'utiliser le domaine que vous avez créé jusqu'à présent.

MobileLineService.java


public class MobileLineService {
    private MobileLineRepository repository;

    // 1.Vérifiez si vous pouvez demander une ligne mobile
    public boolean checkMobileLineApplicable(UserId userId) {
        return repository.isMobileLineApplicable(userId);
    }

    // 2.Vérifiez si vous pouvez annuler l'option de répondeur
    public boolean checkAnswerPhoneCancellable(UserId userId) {
        MobileLineForAnswerPhoneCancellableCheck mobileLine = repository.findForCancellable(userId);

        return mobileLine.getVoiceOption().map(it -> it.getAnswerPhoneOption().isPresent()).orElse(false); //Un peu d'ingéniosité avec map ou Else
    }

    // 3.Obtenez les éléments nécessaires pour annuler l'option Répondeur
    public AnswerPhoneOptionCancellation cancelAnswerPhoneOption(UserId userId) {
        MobileLineForAnswerPhoneCancellation mobileLine = repository.findForCancel(userId);

        return new AnswerPhoneOptionCancellation(
                mobileLine.getId(),                                    //Pas besoin de vérifier isPresent
                mobileLine.getVoiceOption().getId(),                   //Il est confirmé qu'il existe une valeur car le référentiel n'a pas déclenché d'exception
                mobileLine.getVoiceOption().getAnswerPhoneOption().getId()
        );
    }

    // 4.Se référer à la valeur totale de tous les frais d'utilisation mensuels contractuels
    public MonthlyFee totalMonthlyFee(UserId userId) {
        MobileLineForTotalMonthlyFee mobileLine = repository.findForTotalMonthlyFee(userId);

        return MonthlyFee.sum(
                mobileLine.getFee(),                                                          //Un peu d'ingéniosité avec map ou Else
                mobileLine.getVoiceOption().map(it -> it.getFee()).orElse(MonthlyFee.zero()), //Cela peut être un peu plus facile si vous l'ajoutez docilement à 0 yen
                mobileLine.getVoiceOption().flatMap(it -> it.getAnswerPhoneOption()).map(it -> it.getFee()).orElse(MonthlyFee.zero())
        );
    }
}

La grande différence avec le premier MobileLineService est qu'il ne vérifie pas les erreurs système. "Il ne devrait pas y avoir d'élément" croit en la classe retournée par repository.

Vous pouvez donc vous concentrer uniquement sur les calculs qui doivent être effectués pour vos besoins.

En fait, juste un peu plus ...

Maintenant que je suis arrivé jusqu'ici, il y a encore une chose que je veux réparer.

Vous ne pouvez donc vous concentrer que sur les calculs à effectuer pour vos besoins.

C'est la logique métier. Vous pourriez le considérer comme «les calculs nécessaires pour répondre aux spécifications du service».

Vous voulez tester le calcul, non? Vous devriez le vouloir. Il est impossible de ne pas le faire, non? Oui, je veux.

Ici ou alors!

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

Ici ou alors!

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())
);

Êtes-vous sûr de pouvoir déployer sans vérifier l'opération? Est-ce que tout va bien?

Si vous voulez réussir tous les modèles du test service, vous devez préparer un grand nombre de valeurs de retour pour repository.

Il peut s'agir de données factices fictives ou enregistrées dans la base de données, mais vous ne pouvez pas le faire.

Par conséquent, nous devons prêter attention à la description suivante dans le tableau de la description de la première couche.

domaine Décrire la logique métier

Classe également renouvelée! !!

Je ne le publierai qu'une seule fois. C'est la dernière amélioration.

2. Vérifiez si vous pouvez annuler l'option de répondeur

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 {
}

Demandez à la classe racine "Pouvez-vous postuler?"

3. Obtenez les éléments nécessaires pour annuler l'option de répondeur

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
        );
    }
}

J'ai franchi le pas et j'ai décidé d'arrêter la structure de nidification et d'en faire une classe plate. J'ai besoin d'eux de toute façon.

Je viens de dire "une collection de documents pour les informations nécessaires à l'annulation". Si vous dites à la classe racine "Créer les informations nécessaires à l'annulation", elle sera assemblée en utilisant les matériaux. Vous ne pouvez pas voir ce que le matériau est spécifiquement à l'extérieur. C'est encapsulé.

4. Reportez-vous à la valeur totale de tous les frais d'utilisation mensuels contractuels

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;
    }
}

J'étais un peu confus ici, mais préparez des méthodes qui renvoient la somme des prix en dessous d'eux-mêmes, et la classe supérieure s'ajoutera à elle-même. Eh bien, les détails à l'intérieur sont une question triviale, et ici aussi, tout ce que vous avez à faire est de dire "totaliser" à la classe racine.

Dépôt

Je pense qu'il vaut mieux diviser le référentiel en fonction des exigences de la classe à renvoyer. Cela s'est produit lorsque j'ai procédé à l'organisation des paquets. (Récemment, je ne suis toujours pas très confiant.)

Par conséquent, le référentiel est également divisé en autant de classes que le nombre de classes créées.

Je vais omettre le code.

un service

MobileLineService.java


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

    // 1.Vérifiez si vous pouvez demander une ligne mobile
    public boolean checkMobileLineApplicable(UserId userId) {
        return mobileLineRepository
                .isMobileLineApplicable(userId);
    }

    // 2.Vérifiez si vous pouvez annuler l'option de répondeur
    public boolean checkAnswerPhoneCancellable(UserId userId) {
        return forAnswerPhoneCancellableCheckRepository.find(userId)
                .isAnswerPhoneCancellable();
    }

    // 3.Obtenez les éléments dont vous avez besoin pour annuler votre option de répondeur
    public AnswerPhoneOptionCancellation cancelAnswerPhoneOption(UserId userId) {
        return forAnswerPhoneCancellationRepository.find(userId)
                .cancel();
    }

    // 4.Se référer à la valeur totale de tous les frais d'utilisation mensuels contractuels
    public MonthlyFee totalMonthlyFee(UserId userId) {
        return totalMonthlyFeeRepository.find(userId)
                .getTotalMonthlyFee();
    }
}

Vous pouvez maintenant simplement demander la classe racine du repository et demander à la classe racine de faire le calcul! !!

C'est le seul service.

Le calcul et le traitement ont été séparés.

Le test de calcul qui a été déplacé vers la classe de domaine peut être effectué manuellement sans saisir de données factices dans la base de données. Vous pouvez nouveau directement sous la classe racine et en tester autant que vous le souhaitez.

Prise en compte de l'amélioration

Responsabilités

J'ai posté le code trois fois.

Si vous regardez les choses comme ceci, vous pouvez voir que les responsabilités ont été progressivement séparées du service et déplacées vers le domaine. Ou plutôt, le premier service, après tout, était trop dur.

Robustesse

Regardons ça sous un angle légèrement différent

En règle générale, ʻOptional ne devrait pas faire get () . (Je vais omettre les détails, mais en gros, il vaut mieux chercher un plan d'amélioration avec le finaliste après get ()`.)

Au fur et à mesure que nous nous améliorions, get () et ʻorElseThrow () disparaissaient, il n'y avait donc aucune exception autour de ʻOptional.

De plus, en prenant la forme de commander la classe racine, @ Getter a disparu. Cela signifie que l'encapsulation a réussi.

La dernière classe créée est séparée par exigence, donc même si une valeur supplémentaire n'est requise que pour une certaine exigence, la modification n'affectera que cette classe. La classe de service dit simplement "~~" à la classe racine sans connaître les éléments à l'intérieur.

Même si la date de demande est ajoutée au jugement d'annulation, il n'est pas nécessaire de réparer le service, et la recomposition de l'évaluation n'est pas nécessaire juste en cas de calcul du prix.

3 types de "non"

Ordinaire

Il n'y a pas d'exceptions

Non spécifié en tant que branche dans le diagramme de flux de spécification de service 1.png

Erreur commerciale

Il n'y a pas d'exceptions

Spécifié comme une branche dans le diagramme de flux de spécification de service 2.png

Le traitement ultérieur après l'occurrence est également traité normalement selon les spécifications

Puisqu'il est écrit dans la spécification de service, il est détecté par domain qui écrit la logique métier

Si l'état des données est le même, la même erreur commerciale se produira toujours

Erreur système

Utiliser des exceptions

Non spécifié en tant que branche dans le diagramme de flux de spécification de service 3.png

Dans de nombreux cas, le traitement ultérieur ne peut pas être poursuivi après son exécution.

Puisqu'il n'est pas écrit dans la spécification de service et implique des exceptions, il est gardé par "mapper" au lieu d'être détecté par "domaine" qui écrit la logique métier.

Même si l'état des données est le même, cela peut ou non se produire en raison d'une charge ou d'une interruption de la communication. (Si les données sont incohérentes, cela se produira toujours de la même manière)

Classe d'ensemble de somme vs classe spécialisée

Tout d'abord, dans le cas des classes spécialisées, la modification d'une exigence n'en affecte pas une autre.

La classe d'ensemble de somme définit ʻOptionaltoutes les valeurs qui ne peuvent être conservées que pour certaines exigences. (OuListe`)

C'est, par exemple, "La date de demande d'annulation est" vide "sauf lors de l'annulation, mais lors de l'annulation de l'annulation, il devrait y avoir une valeur à la date de demande d'annulation." «Facultatif» qui nécessite une grande quantité de connaissances préalables telles que «Il s'agit toujours d'une liste vide immédiatement après l'application, mais lors de l'annulation, il devrait y avoir une liste contenant 1 à 3 éléments» est créée. (Ou Liste) C'est super épicé. (histoire vraie)

Considérant leur application, leur modification, leur annulation, leur annulation, les éléments optionnels, etc., la classe d'ensemble de somme est couverte par ʻOptional ʻOu Else Throw ("Il devrait y avoir ~ ~")Vous ne pouvez pas facilement imaginer être couvert?

exception

Je ne pense pas que des exceptions dans "domain" devraient être faites.

Puisqu'il s'agit d'un calcul à faire avec domaine, il est hors de question de ne pas avoir de cohérence d'état, C'est parce que je pense que la valeur devrait exprimer l'échec parce que l'erreur est attendue tant qu'elle est calculée comme logique métier.

Poussons toutes les exceptions vers mapper. (Légèrement violent)

Qu'en est-il du «service»? «service» peut être approprié pour «catch». Par exemple, "S'il y a une erreur système, une alarme sera émise."

Ce qui reste dans le service

Cette fois, il n'y avait qu'une seule classe d'itinéraire, mais par exemple Comme "J'ai un contrat pour une option de répondeur" & "Il n'y a pas de montant impayé pour une option de répondeur" & "Deux ans se sont écoulés depuis le contrat" Si cela devient une condition composée, d'autres référentiels et classes racine apparaîtront également.

Je pense qu'il est de la responsabilité de la couche service de les gérer et de gérer les grands domaines qui nécessitent tous ces domaines. maintenant.

Est-ce quelque chose comme une classe qui ne connaît pas la plupart des calculs spécifiques et des erreurs système que la fin devrait prendre en compte, mais qui connaît juste «l'ordre de traitement» et les «caractères entiers»?

tester

Dans le premier exemple, il était nécessaire de saisir des données factices de la base de données afin de couvrir les modèles essentiels de la logique de calcul.

Dans le dernier exemple, le calcul a été séparé en «domaine», vous pouvez donc «nouveau» et le tester avec n'importe quelle valeur que vous aimez.

Complet

J'ai écrit au début:

C'est juste une «classification que j'ai envisagée à ce stade», mais cela rend clairement la portée de la responsabilité de chaque paquet. Nous pensons également qu'il sera plus facile de se rapprocher d'une conception permettant des tests unitaires appropriés, et d'organiser et de maintenir la logique.

En fait, je ne pensais pas beaucoup à ce stade, alors j'ai pensé l'effacer si je ne pouvais pas bien me connecter, mais j'étais soulagé qu'il soit connecté de manière inattendue.

Les erreurs système, le traitement et les calculs se trouvent désormais dans chaque couche, peuvent être testés à l'unité et les changements d'exigences n'affectent plus les autres exigences!

c'est tout

c'est tout.

Pourriez-vous nous dire qu'il y a beaucoup d'avantages à ne considérer «rien»?

ʻOptional est pratique, et surtout, j'aime le Soyu de type échec, mais je ne fais rien de déraisonnable ʻOptional Je pense que comparé aux exigences, il se rapprochera naturellement d'un bon design.

Recommended Posts

Qu'Est-ce que c'est? ~ 3 types de "non" ~
'% 02d' Quel est le% de% 2?
Quel genre de StringUtil est bon
Quel type de méthode est define_method?
Qu'est-ce qu'un test? ・ À propos de l'importance d'un test
Quelle est la structure des données d'ActionText?
Qu'est-ce que Cubby
Qu'est-ce que 'java
Qu'est-ce que maven?
Qu'est-ce que Jackson?
Qu'est-ce que soi
Qu'est-ce que Jenkins
Qu'est-ce que ArgumentMatcher?
Qu'est-ce que IM-Juggling?
Qu'est-ce que les paramètres
Qu'est-ce que SLF4J?
Qu'est-ce que la façade? ??
Qu'est-ce que Java <>?
Qu'est-ce que Gradle?
Qu'est-ce que POJO
Qu'est-ce que 'java
Qu'est-ce que centOS
Qu'est-ce que RubyGem?
Qu'est-ce que before_action?
Qu'est-ce que Docker
Qu'est-ce que Byte?
Qu'est-ce que Tomcat
Qu'est-ce que JSP? ~ Connaissons les bases de JSP !! ~
Aucune instance englobante de type Hoge n'est accessible.
Récupérer le premier jour de la semaine dans les paramètres régionaux actuels (quel jour est-il aujourd'hui?)
Qu'est-ce que l'assemblage Maven?
Vérifiez l'existence de has_many et appartient_to-optional: Qu'est-ce qui est vrai?
Qu'est-ce que `docker-compose up`?
Qu'est-ce qu'un constructeur
Qu'est-ce que vue cli
Qu'est-ce qu'une interface
Qu'est-ce que le moi de Ruby?
Qu'est-ce que le codage en dur?
Qu'est-ce qu'un flux
Qu'est-ce que l'attr_accessor de Ruby?
Qu'est-ce qu'une permission refusée?
Qu'est-ce que le contrôle d'instance?
Qu'est-ce qu'un initialiseur?
Qu'est-ce que Spring Tools 4
Qu'est-ce qu'un opérateur?
Qu'est-ce que l'orientation objet?
Qu'est-ce qu'un modèle MVC?
Qu'est-ce qu'une annotation?
Qu'est-ce que la technologie Java?
Qu'est-ce que Java API-java
Qu'est-ce que @ (variable d'instance)?
Qu'est-ce que l'artefact de Gradle?
Qu'est-ce que l'audit JPA?
[Java] Qu'est-ce que flatMap?