[JAVA] Création d'un exemple de programme en utilisant le problème d'un spécialiste des bases de données avec DDD Improvement 1

introduction

La dernière fois, j'ai écrit un article dans lequel j'ai essayé de créer un exemple de programme avec DDD en utilisant le problème d'un spécialiste des bases de données.

J'ai créé un exemple de programme en utilisant le problème du spécialiste des bases de données dans la conception pilotée par domaine

Merci à tous ceux qui ont commenté dans la section commentaires et sur Twitter. Il y avait beaucoup de choses qui ne se sont pas bien passées, et je pense qu'il est important de continuer à s'améliorer et à produire, donc cet article était celui que je voulais publier un article refactorisé.

Des trois points de terminaison, «Demander une place d'entrée», «Enregistrer les résultats de la loterie» et «Déposer», «Demander une place d'entrée» a été remanié cette fois.

Le code source est ici La version au moment de la publication de cet article est la balise 1.1.

Créer un diagramme de modèle de domaine

J'ai senti que la modélisation n'était pas bien faite, alors @ little_hand_s's [Qu'est-ce que la modélisation DDD et comment la mettre dans le code](https://www.slideshare.net/koichiromatsuoka/domain-modeling- J'ai créé un diagramme de modèle de domaine en référence à la diapositive appelée andcoding).

参加申込.png

La partie correspondant à la règle métier est soufflée des questions d'examen. Toutes ces bulles ne sont pas liées au programme à ce point d'entrée, mais c'était bien de pouvoir organiser la logique à écrire dans la couche de domaine.

De la couche application à la couche domaine

Dans le programme précédent, la partie "Les membres peuvent demander à participer à une seule image d'entrée pour un tournoi" a été divulguée à la couche d'application, alors j'ai essayé de la déplacer vers la couche de domaine.

À @ a-suenami dans la section des commentaires précédente

Par exemple, je pense que la condition selon laquelle "un seul cadre d'entrée peut être appliqué pour un tournoi" est une préoccupation du domaine, donc au lieu d'utiliser if dans le service d'application, créez un type comme la politique d'acceptation des demandes de tournoi (FestivalApplicationPolicy). Je pense qu'il vaut mieux le mettre dans le modèle du domaine.

J'ai reçu l'avis que j'essayais de mettre cela en œuvre à ma manière.

FestivalApplicationPolicy.java


public class FestivalApplicationPolicy {

  private List<Application> applicationList;

  public FestivalApplicationPolicy(List<Application> applicationList) {
    this.applicationList = new ArrayList<>(applicationList);
  }

  /**
   *Renvoie si le membre spécifié par l'argument a déjà demandé à participer au tournoi spécifié par l'argument.
   *Renvoie vrai si déjà appliqué.
   */
  boolean hasAlreadyApplyForSameFestival(MemberId memberId, FestivalId festivalId) {

    for (Application application : applicationList) {
      if (application.festivalId().equals(festivalId)
          && application.memberId().equals(memberId)) {
        return true;
      }
    }

    return false;
  }
}

J'utilise une collection de première classe. J'ai une liste de demandes de participation sur le terrain, et s'il y a une demande de participation avec le même numéro de membre et le même numéro de tournoi, j'ai essayé de juger que la demande de participation était déjà complétée.

J'ai essayé de l'utiliser dans le service de domaine décrit plus tard.

Créer un service de domaine

J'avais besoin de créer une entité pour conserver l'application, et j'ai créé un service de domaine pour créer cette entité. (Le nom est déroutant, mais je le crée dans la couche de domaine au lieu de la couche d'application) Je ne sais toujours pas comment bien utiliser le service de domaine et je me demandais s'il fallait le créer avec la méthode statique dans l'entité de l'application de participation, mais cette fois, je l'ai fait comme ça.

ApplicationService.java


public class ApplicationService {

  private Entry entry;

  private FestivalApplicationPolicy festivalApplicationPolicy;

  /**
   *constructeur.
   */
  public ApplicationService(
      Entry entry, FestivalApplicationPolicy festivalApplicationPolicy) {

    this.entry = entry;
    this.festivalApplicationPolicy = festivalApplicationPolicy;
  }

  /**
   *Créer et renvoyer un objet d'application de participation.
   * @retour Objet de demande de participation
   */
  public Application createApplication(MemberId memberId, LocalDate applicationDate)
      throws EntryStatusIsNotRecruitingException,
      HasAlreadyApplyForSameFestivalException {

    //Les demandes de participation ne seront acceptées que pendant que le créneau d'inscription est ouvert
    if (entry.entryStatus() != EntryStatus.recruiting) {
      throw new EntryStatusIsNotRecruitingException();
    }

    //Un jugement basé sur la date de début du recrutement et la date de fin du recrutement doit être effectué si le statut de la base d'entrée est en cours de recrutement.
    //Puisqu'aucune erreur ne devrait se produire, j'ose lancer une IllegalStateException ici.
    if (applicationDate.compareTo(entry.applicationStartDate()) < 0) {
      throw new IllegalStateException("Le tournoi spécifié n'a pas encore commencé à recruter");
    }

    if (applicationDate.compareTo(entry.applicationEndDate()) > 0) {
      throw new IllegalStateException("La période de recrutement pour le tournoi désigné est terminée");
    }


    //Les membres ne peuvent demander à participer qu'à un seul emplacement pour un tournoi
    if (festivalApplicationPolicy.hasAlreadyApplyForSameFestival(
        memberId, entry.festivalId())) {
      throw new HasAlreadyApplyForSameFestivalException();
    }

    return Application.createEntityForEntry(
        entry.festivalId(),
        memberId,
        entry.entryId(),
        applicationDate
    );
  }
}

Après avoir coché "Accepter la demande de participation uniquement pendant le recrutement du cadre de participation", "Les membres ne peuvent demander à participer que pour un seul cadre de participation pour un tournoi", etc., s'il n'y a pas d'erreur, l'objet de la classe de candidature doit être conservé Est généré et renvoyé.

Nous créons également des classes d'exception métier ʻEntryStatusIsNotRecruitingException et HasAlreadyApplyForSameFestivalException`. Cela a également été donné par @ a-suenami

Je veux gérer la chaîne de message elle-même à montrer à l'utilisateur dans le service d'application, mais je pense que le type d'erreur qui existe peut être un objet de domaine, j'ai donc envie de le renvoyer comme type d'énumération.

Je l'ai essayé en référence au commentaire. Cependant, j'utilise des exceptions au lieu de types d'énumération. Je me demandais si le champ de classe d'exception métier avait un type d'énumération qui montre le type d'erreur, mais je pensais que ces deux erreurs intéressaient également le domaine d'application de participation, j'ai donc créé une classe d'exception dans le package de domaine d'application de participation. Cette exception est interceptée dans la couche application afin que les messages d'erreur puissent être traités.

Comparaison des niveaux d'application

Les programmes de demande de participation à la couche application la dernière fois et cette fois sont les suivants.

** Dernière fois **

ApplicationCommandService.java


  /**
   *Demander un créneau d'inscription.
   */
  public void applyForEntry(ApplyForEntryRequest request) {

    final FestivalId festivalId = request.festivalId();
    final MemberId memberId = request.memberId();
    final EntryId entryId = request.entryId();
    final LocalDate applicationDate = request.getApplicationDate();

    final Member member = memberRepository.findMember(request.memberId());
    if (member == null) {
      throw new BusinessErrorException("C'est un membre qui n'existe pas");
    }

    final Application alreadyApplication =
        applicationRepository.findApplication(festivalId, memberId);

    if (alreadyApplication != null) {
      throw new BusinessErrorException("J'ai déjà postulé pour le tournoi spécifié");
    }

    final Entry entry = entryRepository.findEntry(festivalId, entryId);

    if (entry == null) {
      throw new BusinessErrorException("C'est un cadre d'entrée qui n'existe pas");
    }

    final Application application = Application.createEntityForEntry(
        festivalId,
        memberId,
        entryId,
        applicationDate
    );

    entry.validateAndThrowBusinessErrorIfHasErrorForApplication(application);

    entry.incrementApplicationNumbers();
    entryRepository.saveEntry(entry);

    applicationRepository.addApplication(application);
  }

cette fois

ApplicationCommandService.java


  /**
   *Demander un créneau d'inscription.
   */
  public void applyForEntry(ApplyForEntryRequest request) {

    final FestivalId festivalId = request.festivalId();
    final EntryId entryId = request.entryId();
    final MemberId memberId = request.memberId();
    final LocalDate applicationDate = request.getApplicationDate();

    final Member member = memberRepository.findMember(memberId);
    if (member == null) {
      throw new BusinessErrorException("C'est un membre qui n'existe pas");
    }

    final Entry entry = entryRepository.findEntry(festivalId, entryId);
    if (entry == null) {
      throw new BusinessErrorException("C'est un cadre d'entrée qui n'existe pas");
    }

    final FestivalApplicationPolicy festivalApplicationPolicy =
        applicationRepository.createFestivalApplicationPolicy(festivalId, memberId);

    final ApplicationService applicationService =
        new ApplicationService(entry, festivalApplicationPolicy);

    final Application application;
    try {
      application = applicationService.createApplication(memberId, applicationDate);
    } catch (EntryStatusIsNotRecruitingException e) {
      throw new BusinessErrorException("Le tournoi désigné ne recrute pas actuellement");
    } catch (HasAlreadyApplyForSameFestivalException e) {
      throw new BusinessErrorException("J'ai déjà postulé pour le tournoi spécifié");
    }

    entry.incrementApplicationNumbers();
    entryRepository.saveEntry(entry);

    applicationRepository.addApplication(application);
  }

Générez le service de domaine ʻApplicationService avec l'objet de cadre d'entrée ʻEntry et le nouveau FestivalApplicationPolicy comme arguments, et générez l'entité d'application de participation ʻApplicaiton avec ʻApplicationService et rendez-le persistant Je l'ai fait. Cela ne semble pas beaucoup de changement, mais au moins nous avons pu déplacer "les membres ne peuvent demander qu'un seul emplacement d'entrée pour un tournoi" vers la couche de domaine.

à la fin

Dans ce cas, cela peut ne pas être très avantageux, mais en répétant les améliorations qui apportent les intérêts du domaine à la couche de domaine, le programme sera facile à changer dans les affaires réelles! !! Cela devrait être: sweat_smile: De plus, en répétant les améliorations en refactorisant, les compétences en conception devraient s'améliorer, donc je vais refactoriser les points d'entrée «enregistrer les résultats de loterie» et «déposer» dans un proche avenir et écrire un article.

Merci d'avoir lu jusqu'ici. Commentaires de tout le monde Je suis très heureux et j'ai beaucoup à apprendre, alors je vous serais reconnaissant si vous pouviez commenter.

Recommended Posts

Création d'un exemple de programme en utilisant le problème d'un spécialiste des bases de données dans DDD Improvement 2
Création d'un exemple de programme en utilisant le problème d'un spécialiste des bases de données avec DDD Improvement 1
J'ai essayé de créer un exemple de programme en utilisant le problème du spécialiste des bases de données dans la conception pilotée par domaine
Exemple de programme qui renvoie la valeur de hachage d'un fichier en Java
Utilisation de la base de données (SQL Server 2014) à partir d'un programme Java 04/01/2018
Un programme qui compte le nombre de mots dans une liste
Un mémorandum du problème FizzBuzz
Ordre de traitement dans le programme
Création d'un servlet dans l'environnement Liberty
J'ai fait un exemple de la façon d'écrire un délégué dans Swift UI 2.0 à l'aide de MapKit
Mesurer la taille d'un dossier avec Java
Exemple de code pour attribuer une valeur dans le fichier de propriétés à un champ du type attendu
J'ai essayé d'utiliser une connexion à une base de données dans le développement Android
À propos du problème de blocage dans le traitement parallèle dans la version 4.0 de gem'sprockets
J'ai écrit un diagramme de séquence de l'exemple j.u.c.Flow
[Ruby] Prenez l'habitude d'utiliser la méthode dup lors de la copie d'une variable chaîne
Un programme (Java) qui génère la somme des nombres pairs et impairs dans un tableau
Exemple de code pour obtenir les valeurs des principaux types SQL dans Java + Oracle Database 12c
Créer une base de données de tous les livres qui ont circulé au Japon au siècle dernier
J'ai essayé de créer un programme en Java qui résout le problème du voyageur de commerce avec un algorithme génétique
Programme de création de modèle lors de l'utilisation de la fonction de rappel dans Slack
Une explication rapide des cinq types de statique Java
Procédure de publication d'une application à l'aide d'AWS (4) Création d'une base de données
Explication de Ruby on rails pour les débutants ③ ~ Création d'une base de données ~
Trouvez le nombre de jours dans un mois avec Kotlin