[JAVA] Ich habe versucht, ein Beispielprogramm mit dem Problem des Datenbankspezialisten für domänengesteuertes Design zu erstellen

Einführung

Freut mich, dich kennenzulernen. Ich heiße Kotobukiyama. Dies ist Qiitas erster Beitrag! !!

Macht ihr domänengetriebenes Design? Wenn Sie domänengesteuertes Design verwenden, können Sie ein Programm mit hoher Wartbarkeit schreiben, daher möchte ich es ausprobieren! !! Es wird sein. Aber es ist schwierig, nicht wahr? Domain-gesteuertes Design ... Es gibt mehrere Bücher, aber sie sind schwer zu verstehen. Viele Leute möchten vielleicht mehr praktische Codebeispiele sehen. In den letzten Jahren ist domänengesteuertes Design jedoch zu einem heißen Thema bei Studiensitzungen und Veranstaltungen geworden, und ich bin der Meinung, dass es mehr Möglichkeiten gibt, Beispielprogramme zu sehen. Aus diesem Grund wollte ich selbst ein Beispielprogramm schreiben und praktisches Know-how erwerben. Deshalb habe ich ein Beispielprogramm mit den Prüfungsfragen von IPA-Datenbankspezialisten als Thema erstellt.

Der Quellcode lautet hier Die Version zum Zeitpunkt der Veröffentlichung dieses Artikels ist Tag 1.0.

Es war überhaupt kein domänengetriebenes Design, also schien es, als würden viele Tsukkomi hereinkommen, aber ich habe es geschafft, es in Form zu bringen. Dieses Mal möchte ich darüber sprechen, was gut war und was nicht, als ich es tatsächlich ausprobierte.

Anwendungsspezifikationen usw.

Problem

IPA Spring 2019 Database Specialist Exam Dies ist ein Beispielprogramm, das durch domänengesteuertes Design des Turniermanagementsystems erstellt wurde und in Frage 1 von 13 Uhr gestellt wurde.

Problem PDF-Download-Seite

Einschränkungen

ER-Diagramm

Das Tischdesign entspricht der gestellten Frage.

er diagram.png

Klassen Diagramm

Ich habe nur die Klasse des Domänenmodells extrahiert und ein Klassendiagramm erstellt. Um ein Durcheinander der Zeilen zu vermeiden, habe ich einige Assoziationen geschrieben. Da die Entry-Klasse eine abstrakte Klasse ist, werden nur das Verhältnis der Verallgemeinerung der konkreten Klasse, die sie implementiert, und die Vielzahl der MemberPoints-Klasse und der MemberPoint-Klasse beschrieben. Ich habe das Klassendiagramm später geschrieben, aber es gibt nur wenige Methoden ... (Ist das wirklich ein domänengesteuertes Design ...)

class diagram.png

API-Endpunkt

Dieses Mal habe ich die folgenden drei Endpunkte erstellt.

Bewerben Sie sich für den Einstiegsplatz

POST : /applications/entry

Registrieren Sie das Lotterieergebnis

POST : /lottery-entry-result

Machen Sie eine Einzahlung

POST : /applications/payment

Was war gut und was nicht

Die Anwendungsschicht ist aufgebläht

Wenn Sie eine Methode im Domänenmodell erstellen könnten, wäre die Anwendungsschicht ** relativ dünn, und wenn Sie im Domänenmodell kaum eine Methode erstellen könnten, wäre die Anwendungsschicht aufgebläht. (Es ist ein natürliches Ergebnis ...)

Die Methoden der Anwendungsschicht der drei Endpunkte sind wie folgt

ApplicationCommandService.java


  /**
   *Bewerben Sie sich für den Einstiegsplatz.
   */
  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("Es ist ein Mitglied, das nicht existiert");
    }

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

    if (alreadyApplication != null) {
      throw new BusinessErrorException("Ich habe mich bereits für das angegebene Turnier beworben");
    }

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

    if (entry == null) {
      throw new BusinessErrorException("Es ist ein Eintragsrahmen, der nicht existiert");
    }

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

    entry.validateAndThrowBusinessErrorIfHasErrorForApplication(application);

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

    applicationRepository.addApplication(application);
  }

LotteryEntryResultCommandService.java


  /**
   *Registrieren Sie das Lotterieergebnis.
   */
  public void registerLotteryEntryResult(RegisterLotteryEntryResultRequest request) {

    final Member member = memberRepository.findMember(request.memberId());
    if (member == null) {
      throw new BusinessErrorException("Es ist ein Mitglied, das nicht existiert");
    }

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

    if (entry == null) {
      throw new BusinessErrorException("Es ist ein Eintragsrahmen, der nicht existiert");
    }

    if (!entry.isLotteryEntry()) {
      throw new BusinessErrorException("Es ist kein Lotterieeintrittsrahmen");
    }

    if (entry.entryStatus() != EntryStatus.underLottery) {
      throw new BusinessErrorException("Die Lotterie hat noch nicht begonnen");
    }

    LotteryEntryResult already = lotteryEntryResultRepository.findLotteryEntryResult(
        request.festivalId(),
        request.memberId(),
        request.entryId()
    );
    if (already != null) {
      throw new BusinessErrorException("Das Lotterieergebnis wurde bereits registriert");
    }

    Application application = applicationRepository.findApplication(
        request.festivalId(),
        request.memberId());
    if (application == null) {
      throw new BusinessErrorException("Ich habe mich nicht für das Zielturnier beworben");
    }

    //Wenn es nicht der von Ihnen beantragte Einstiegsplatz ist, handelt es sich dann um einen mehrstufigen Lotterieslot für den von Ihnen beantragten Einstiegsplatz?
    if (!(application.entryId().equals(request.entryId()))) {
      Entry firstEntry = entryRepository.findEntry(request.festivalId(), application.entryId());
      if (!(((LotteryEntry)firstEntry).followingEntryId().equals(request.entryId()))) {
        throw new BusinessErrorException("Es ist ein Einstiegsrahmen, der nicht der Lotterie unterliegt");
      }

      LotteryEntryResult firstEntryResult =
          lotteryEntryResultRepository.findLotteryEntryResult(
              application.festivalId(),
              application.memberId(),
              application.entryId());
      if (firstEntryResult == null) {
        throw new BusinessErrorException("Das Lotterieergebnis des von Ihnen beantragten Anmeldungsrahmens wurde noch nicht registriert");
      }

      if (firstEntryResult.lotteryResult() == LotteryResult.winning) {
        throw new BusinessErrorException("Da Sie bereits gewonnen haben, können Sie das Lotterieergebnis nicht für den nachfolgenden Startplatz registrieren.");
      }
    }

    LotteryEntryResult lotteryEntryResult = new LotteryEntryResult(
        request.festivalId(),
        request.memberId(),
        request.entryId(),
        request.getLotteryResult()
    );

    lotteryEntryResultRepository.saveLotteryEntryResult(lotteryEntryResult);
  }

PaymentCommandService.java


  /**
   *Machen Sie eine Einzahlung.
   */
  public void payToApplication(PaymentRequest request) {

    final FestivalId festivalId = request.festivalId();
    final MemberId memberId = request.memberId();

    Application application = applicationRepository.findApplication(festivalId, memberId);

    if (application == null) {
      throw new BusinessErrorException("Ich habe mich nicht für das Zielturnier beworben");
    }

    Entry entry = entryRepository.findEntry(festivalId, application.entryId());
    if (entry.isLotteryEntry()) {
      //Wenn der Zieleintrag eine Lotterie ist, prüfen Sie, ob er gewonnen hat
      LotteryEntryResult entryResult = lotteryEntryResultRepository.findLotteryEntryResult(
          festivalId, memberId, entry.entryId());

      if (entryResult.lotteryResult() == LotteryResult.failed) {
        EntryId followingEntryId = ((LotteryEntry)entry).followingEntryId();
        if (followingEntryId == null) {
          throw new BusinessErrorException("Ich habe das Zielturnier nicht gewonnen");
        } else {
          LotteryEntryResult followingEntryResult =
              lotteryEntryResultRepository.findLotteryEntryResult(
                  festivalId, memberId, followingEntryId);

          if (followingEntryResult.lotteryResult() == LotteryResult.failed) {
            throw new BusinessErrorException("Ich habe das Zielturnier nicht gewonnen");
          }
        }
      }
    }

    PointAmount usePoints = new PointAmount(request.getUsePoints());
    if (usePoints.isPositive()) {
      MemberPoints memberPoints = memberPointRepository.findMemberPoints(memberId);
      memberPoints.usePoints(request.getPaymentDate(), usePoints);

      memberPointRepository.saveMemberPoints(memberPoints);
    }

    application.pay(request.getPaymentDate(), usePoints);
    applicationRepository.saveApplication(application);
  }

Ich denke, dass "Beantragen des Eintragsrahmens" und "Bezahlen" immer noch besser sind, aber "Registrieren der Lotterieergebnisse" hat dazu geführt, dass in der Anwendungsebene viele geschäftliche Fehlerprüfungen verarbeitet wurden. Ich fragte mich, ob ich zu solchen Zeiten einen Domänendienst erstellen sollte, aber diesmal hörte ich auf, weil es den Anschein hatte, als würde das Erstellen einer Domänendienstklasse nur den Ort ändern, an dem ich den Code geschrieben habe. Erstens habe ich das Gefühl, dass das Design der Entität in die Datenbank gezogen wurde und nicht gut funktioniert.

Für diejenigen, die "Einreise beantragen" und "Einzahlung", habe ich das Gefühl, dass ich ein wenig objektorientiert gestalten konnte, also möchte ich Sie vorstellen.

Bewerben Sie sich für den Einstiegsplatz

Es gibt ein Muster aus "Wer zuerst kommt, mahlt zuerst" und "Lotterierahmen" im Eingaberahmen, aber dieses Mal habe ich den Eingaberahmen mit einer abstrakten Klasse erstellt und den Rahmen "Wer zuerst kommt, mahlt zuerst" und den Lotterierahmen zu einer konkreten Klasse gemacht.

Wenn Sie sich beispielsweise bewerben, wird die Anzahl der Bewerber erhöht, aber jeder hat die folgende Implementierung.

FirstArrivalEntry.java


/**Eingangsrahmen "Wer zuerst kommt, mahlt zuerst". */

  /**
   *Erhöhen Sie die Anzahl der Bewerber.Wenn die Anzahl der Bewerber die Kapazität erreicht hat, ändern Sie diese, um die Teilnehmer zu bestätigen.
   */
  @Override
  public void incrementApplicationNumbers() {
    applicationNumbers = applicationNumbers.increment();

    if (capacity.same(applicationNumbers)) {
      entryStatus = EntryStatus.participantConfirmation;
    }
  }

LotteryEntry.java


/**Lotterieeintragsrahmen. */

  @Override
  public void incrementApplicationNumbers() {
    applicationNumbers = applicationNumbers.increment();
  }

Im Fall von "Wer zuerst kommt, mahlt zuerst" muss der Antrag geschlossen werden, wenn die Anzahl der Antragsteller die Kapazität erreicht. Auf der Clientseite wird die Methode für die Schnittstelle wie unten gezeigt ausgeführt, daher frage ich mich, ob sie objektorientiert sein könnte.

ApplicationCommandService.java


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

Übrigens werden in dem Teil, der Entity von DB erwirbt, Daten von DTB von MyBatis erfasst, und die zu generierenden Klassen werden nach dem Frame "Wer zuerst kommt, mahlt zuerst" oder dem Lotterierahmen unterteilt. Ich frage mich, ob dies auch der Teil ist, der funktioniert hat.

MybatisEntryRepository.java


  public Entry findEntry(FestivalId festivalId, EntryId entryId) {

    EntryDto dto = entryMapper.selectEntry(festivalId, entryId);

    if (dto == null) {
      return null;
    }

    if (dto.firstArrivalLotteryType == FirstArrivalLotteryType.firstArrival) {

      return new FirstArrivalEntry(
          dto.festivalId,
          dto.entryId,
          dto.entryName,
          dto.entryDescription,
          dto.eventCode,
          dto.capacity,
          dto.participationFees,
          dto.applicationNumbers,
          dto.applicationStartDate,
          dto.applicationEndDate,
          dto.entryStatus);
    } else {

      return new LotteryEntry(
          dto.festivalId,
          dto.entryId,
          dto.entryName,
          dto.entryDescription,
          dto.eventCode,
          dto.capacity,
          dto.participationFees,
          dto.applicationNumbers,
          dto.applicationStartDate,
          dto.applicationEndDate,
          dto.entryStatus,
          dto.lotteryDate,
          dto.followingEntryId);
    }
  }

Machen Sie eine Einzahlung

Sie können Punkte bei der Einzahlung verwenden, aber dieser Punkt hat ein Ablaufdatum von einem Jahr und es ist eine Spezifikation, dass derjenige mit dem nächstgelegenen Ablaufdatum verwendet wird, daher denke ich, dass dies gut funktioniert hat.

Wir verwenden die Sammlungsobjekte, die in Auf dem Gebiet nützliche Prinzipien des Systemdesigns eingeführt wurden.

MemberPoint.java


public class MemberPoint implements Entity {

  private MemberId memberId;

  private LocalDate givenPointDate;

  private PointAmount givenPoint;

  private PointAmount usedPoint;

  /**
   *Verwenden Sie Punkte für den im Argument verwendeten Wert.
   */
  void use(BigDecimal value) {
    BigDecimal totalUsedPointAmountValue = usedPoint.value().add(value);

    if (totalUsedPointAmountValue.compareTo(givenPoint.value()) > 0) {
      throw new IllegalArgumentException("Es gibt mehr verwendete Punkte als gewährte Punkte");
    }

    usedPoint = new PointAmount(totalUsedPointAmountValue);
  }
}

MemberPoints.java


public class MemberPoints {

  private List<MemberPoint> list;

  /**
   *Bestimmen Sie, ob der Punkt des Arguments verwendet werden kann, und verwenden Sie ihn ab dem Punkt nahe dem Ablaufdatum.
   *Ändern Sie den Status des MemberPoint-Objekts, das beibehalten werden soll.
   */
  public void usePoints(LocalDate paymentDate, PointAmount pointAmount) {

    //Verwenden Sie Punkte aus den bisher angegebenen Punkten, bis dieser Wert Null wird
    BigDecimal x = pointAmount.value();

    for (MemberPoint memberPoint : list) {

      //Ablaufdatum prüfen
      LocalDate expirationDate = memberPoint.givenPointDate().plusYears(1);
      if (paymentDate.compareTo(expirationDate) > 0) {
        continue;
      }

      //Punktausgleich prüfen
      BigDecimal availableUsePoint = memberPoint.givenPoint().value()
          .subtract(memberPoint.usedPoint().value());
      if (availableUsePoint.compareTo(BigDecimal.ZERO) == 0) {
        continue;
      }

      if (availableUsePoint.compareTo(x) <= 0) {
        memberPoint.use(availableUsePoint);
        x = x.subtract(availableUsePoint);
      } else {
        memberPoint.use(x);
        x = BigDecimal.ZERO;
        break;
      }
    }

    //Verwenden Sie Punkte von dem Punkt mit dem nächstgelegenen Ablaufdatum und Fehler, wenn die Anzahl der Punkte, die Sie verwenden möchten, nicht erreicht wird
    if (x.compareTo(BigDecimal.ZERO) > 0) {
      throw new BusinessErrorException("Unzureichende Punkte");
    }
  }
}

Vom Kunden aus konnte ich den Punktestand überprüfen und den Status mehrerer Mitgliedspunkte mit dem folgenden Code ändern.

MemberPoints memberPoints = memberPointRepository.findMemberPoints(memberId);
memberPoints.usePoints(request.getPaymentDate(), usePoints);
memberPointRepository.saveMemberPoints(memberPoints);

schließlich

Am Ende wurde es ein vernünftiges Volumen, und ich bin froh, dass ich ein relativ praktisches Beispiel erstellen konnte, einschließlich Validierung und Speichern in der Datenbank. Was ich jedoch tatsächlich versucht habe und verbessern wollte, ist wie folgt.

Um diese zu verbessern, werde ich in verschiedenen Jahren versuchen, Probleme umzugestalten oder zu üben (vielleicht hätte ich ein Problem mit mehr Berechnungen wählen sollen).

Wenn Sie diesen Artikel lesen und eine Meinung wie "Ist es nicht besser, wenn Sie dies tun?" Haben, würde ich mich freuen, wenn Sie einen Kommentar abgeben könnten. Danke, dass Sie so weit gelesen haben! !!

Recommended Posts

Ich habe versucht, ein Beispielprogramm mit dem Problem des Datenbankspezialisten für domänengesteuertes Design zu erstellen
Erstellen eines Beispielprogramms mit dem Problem eines Datenbankspezialisten für DDD-Verbesserung 2
Erstellen eines Beispielprogramms mit dem Problem eines Datenbankspezialisten mit DDD-Verbesserung 1
Ich habe einen RESAS-API-Client in Java erstellt
Ich habe versucht, die Beispielanwendung gemäß der Idee des Buches "Micro Service Architecture" in einen Mikrodienst zu verwandeln.
Ich habe versucht, ein übergeordnetes Wertklasseobjekt in Ruby zu erstellen
Ich habe versucht, den CPU-Kern mit Ruby voll auszunutzen
Ich habe versucht, mit AI "A3RT" eine Talk-App in Java zu erstellen.
Ich habe ein Beispiel erstellt, wie ein Delegat in Swift UI 2.0 mit MapKit geschrieben wird
Ich habe versucht, eine Datenbankverbindung in der Android-Entwicklung zu verwenden
Ich bin gestolpert, als ich versucht habe, neo4j in der jenv-Umgebung zu verwenden. Machen Sie sich also eine Notiz
Ich habe versucht, das Problem der "mehrstufigen Auswahl" mit Ruby zu lösen
Ich habe versucht, den Weihnachtsbaum in einem Lebensspiel zu beleuchten
[Unity] Ich habe mit NWPathMonitor ein natives Plug-In UniNWPathMonitor erstellt
[Java] Ich habe versucht, mit der Grabmethode ein Labyrinth zu erstellen ♪
Ich habe versucht, das Problem des Google Tech Dev Guide zu lösen
[Rails] Implementierung einer mehrschichtigen Kategoriefunktion unter Verwendung der Abstammung "Ich habe versucht, ein Fenster mit Bootstrap 3 zu erstellen"
Nachdem ich Progate gelernt hatte, versuchte ich, eine SNS-Anwendung mit Rails in der lokalen Umgebung zu erstellen
Ich habe ein Kalenderproblem mit Ruby versucht
Ich habe versucht, eine einfache Gesichtserkennungs-Android-Anwendung mit OpenCV zu erstellen
Ich habe versucht, die wichtigsten Punkte des gRPC-Designs und der Entwicklung zusammenzufassen
Ich habe versucht, das Problem der Tribonacci-Sequenz in Ruby mit Wiederholung zu lösen.
Beispielprogramm, das den Hashwert einer Datei in Java zurückgibt
Machen Sie einen Rand links vom TextField
Ich habe versucht, die Sitzung in Rails zu organisieren
Was ich versucht habe, als ich alle Felder einer Bohne bekommen wollte
Ich habe versucht, das Problem der Tribonacci-Sequenz in Ruby zu lösen (Zeitlimit 10 Minuten).
Ich wollte (a == 1 && a == 2 && a == 3) in Java wahr machen
Ich habe ein Programm erstellt, das aus dem mit Java überladenen Prozess nach der Zielklasse sucht
So erstellen Sie eine eindeutige Datenkombination in der Schienen-Zwischentabelle
[Kleine Geschichte] Ich habe versucht, die Java-ArrayList etwas komfortabler zu gestalten
Ich habe versucht, einen Server mit Netty zu implementieren
Ich habe versucht, den Profiler von IntelliJ IDEA zu verwenden
Ich habe versucht, eine Webanwendung zu erstellen, die Tweets mit einer Vue-Word-Cloud durchsucht und die Tendenz untersucht, was im zugehörigen Profil geschrieben steht
[Swift] Ich habe bereits viele Informationen, aber ich habe versucht, die Besetzung (as, as !, As?) Auf meine eigene Weise zusammenzufassen.
Ich habe versucht, ein Tool zum Vergleichen von Amazon-Produktpreisen auf der ganzen Welt mit Java, der Amazon Product Advertising API und der Currency API (29.01.2017) zu erstellen.
Ich habe versucht, eine Nachrichtenfunktion der Rails Tutorial-Erweiterung (Teil 1) zu erstellen: Erstellen Sie ein Modell
[Rails] Ich möchte das Linkziel von link_to auf einer separaten Registerkarte anzeigen
Ich habe versucht, den Mechanismus von Emscripten mit einem deutschen Löser zu untersuchen
Beispielcode zum Zuweisen eines Werts in der Eigenschaftendatei zu einem Feld des erwarteten Typs
Ich habe versucht, die Server-Push-Funktion von Servlet 4.0 zu verwenden
05. Ich habe versucht, die Quelle von Spring Boot zu löschen
Ich habe versucht, die Kapazität von Spring Boot zu reduzieren
Ich habe versucht, eine Clova-Fähigkeit in Java zu erstellen
Ich habe ein Sequenzdiagramm des j.u.c.Flow-Beispiels geschrieben
Verwenden der Datenbank (SQL Server 2014) aus einem Java-Programm 2018/01/04
Ich habe versucht, die Methode der gegenseitigen Teilung von Eugrid in Java zu implementieren
Wir haben eine Umgebung zum Ausführen von Komponententests mithilfe der Oracle-Datenbank (oracle12c) auf dem Docker-in-Docker-Image (dind) von GitLab-CI erstellt.
[VBA] Ich habe ein Tool erstellt, um den primitiven Typ der von Hibernate Tools generierten Entity-Klasse in den entsprechenden Referenztyp zu konvertieren.
Ich habe mir die Rosen von Versailles angesehen und versucht, das Schlusslied in Java zu reproduzieren
Ich habe versucht, eine Antwortfunktion für die Rails Tutorial-Erweiterung (Teil 3) zu erstellen: Ein Missverständnis der Spezifikationen wurde behoben
Beispielcode zum Abrufen der Werte der wichtigsten SQL-Typen in Java + Oracle Database 12c
Ich habe versucht, das Problem mit dem Ruby-Bonusgetränk zu lösen (es gibt ein Beispiel für die Antwort).
Ich habe versucht, die Asset-Management-Anwendung mit dem Emulator des verteilten Hauptbuchs Scalar DLT zu berühren