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.
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.
Das Tischdesign entspricht der gestellten Frage.
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 ...)
Dieses Mal habe ich die folgenden drei Endpunkte erstellt.
POST : /applications/entry
POST : /lottery-entry-result
POST : /applications/payment
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.
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);
}
}
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);
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! !!