Ich denke, es ist notwendig, beim Schreiben von Code in der Praxis viel "nichts" zu berücksichtigen, beispielsweise, dass der Wert in einer bestimmten Situation nicht vorhanden ist oder das Ergebnis nicht erhalten wird, obwohl auf die Datenbank verwiesen wird. Schließlich gibt es viele Techniken, um Dinge auszudrücken, die nicht existieren, wie z. B. "null", "optional" und "nullObject pattern".
Ich möchte zusammenfassen, was ich über den Umgang damit denke.
Klar, ich liebe "Optional" und "Entweder", aber dieses Mal spreche ich nicht über Techniken.
Selbst wenn ich in einem Wort "Nein" sage, habe ich in letzter Zeit das Gefühl, dass es einige Typen gibt. Dieses Mal möchte ich die Arten von "nichts" zusammenfassen.
Es ist nur eine "Klassifizierung, die ich an dieser Stelle in Betracht gezogen habe", aber sie macht den Verantwortungsbereich für jedes Paket klar. Wir glauben auch, dass es einfacher sein wird, einem Design näher zu kommen, das ordnungsgemäße Unit-Tests ermöglicht, und die Logik zu organisieren und zu warten.
Natürlich gibt es viele verschiedene Designs, daher kann ich nichts sagen, aber hier nehmen wir den folgenden Umriss an. Sollte es als "Yurufuwa DDD Style Layer Design" verstanden werden? "... ??
Name | Überblick | Bemerkungen |
---|---|---|
api | Es ist der Ausgangspunkt der Verarbeitung und nutzt den Service Dies kann eine URL oder ein Befehlszeilentool sein |
Wird diesmal nicht angezeigt |
service | Behandeln Sie die Domäne und realisieren Sie die Verarbeitung, die das System ausführen soll | |
domain | Beschreiben der Geschäftslogik | |
repository | Schnittstelle zur Interaktion mit Datenbanken und externen Systemen | Als Schnittstelle in Domain platzieren |
mapper | Repository implementieren | Diesmal nicht implementiert |
Wie üblich bereiten wir ein passendes Thema vor. Diesmal scheint es, dass ein bestimmter Telekommunikationsanbieter Festnetz- und Mobilfunkverträge verwaltet.
Wir werden ein System realisieren, das Verträge mit bestimmten Mitgliedern verwaltet.
Mitglieder können bis zu einer Mobilfunkleitung und einer Festnetzleitung haben. (Feste Linie erscheint diesmal nicht)
Die mobile Leitung verfügt über bis zu 1 Sprachoption. Die Sprachoption umfasst bis zu einen Anrufbeantworter.
Basierend auf der obigen Datenstruktur sind die folgenden Anforderungen erfüllt.
Erfüllen Sie die folgenden vier Anforderungen. Geben Sie für die gesamte Verarbeitung die Mitglieds-ID ein.
Offensichtlich "nicht" ist besetzt ...
domain Zunächst werde ich ehrlich gesagt "Mobilfunk-> Sprachoption-> Anrufbeantworteroption" mit "Optional" ausdrücken. (Dieses Mal muss die Technik des "Nicht" nicht "Optional" sein, aber da es die einfachste ist, werde ich alles unten mit "Optional" beschreiben. Die Essenz ist sogar mit "Null" fast dieselbe.)
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);
}
Es ist einfach.
Es hat einen Preis, den Sie möglicherweise benötigen, und bis zu einem Unterelement.
(MonthlyFee
und XxxId
sind nur primitive Wrapper)
repository Als nächstes kommt "Repository". Sie müssen sich jedoch nur auf die Stammklasse beziehen.
MobileLineRepository.java
public interface MobileLineRepository {
Optional<MobileLine> findMobileLine(UserId userId);
}
Mitglieder haben möglicherweise keine Mobilfunkleitung, daher ist dies "optional". Bild der externen Schlüsselreferenz.
service Schließlich gibt es "Service", aber hier verbinden wir uns einfach.
MobileLineService.java
public class MobileLineService {
private MobileLineRepository repository;
// 1.Prüfen Sie, ob Sie eine Mobilfunkleitung beantragen können
public boolean checkMobileLineApplicable(UserId userId) {
Optional<MobileLine> mobileLineOptional = repository.findMobileLine(userId);
return !mobileLineOptional.isPresent();
}
// 2.Überprüfen Sie, ob Sie die Anrufbeantworteroption abbrechen können
public boolean checkAnswerPhoneCancellable(UserId userId) {
Optional<MobileLine> mobileLineOptional = repository.findMobileLine(userId);
MobileLine mobileLine = mobileLineOptional
.orElseThrow(() -> new RuntimeException("Mobilfunkleitung nicht gefunden"));
if (mobileLine.getVoiceOption().isPresent()) {
return mobileLine.getVoiceOption().get().getAnswerPhoneOption().isPresent();
} else {
return false;
}
}
// 3.Holen Sie sich die Elemente, die zum Abbrechen der Anrufbeantworteroption erforderlich sind
public AnswerPhoneOptionCancellation getAnswerPhoneOptionIdForCancellation(UserId userId) {
Optional<MobileLine> mobileLineOptional = repository.findMobileLine(userId);
MobileLine mobileLine = mobileLineOptional
.orElseThrow(() -> new RuntimeException("Mobilfunkleitung nicht gefunden"));
VoiceOption voiceOption = mobileLine.getVoiceOption()
.orElseThrow(() -> new RuntimeException("Sprachoption nicht gefunden"));
AnswerPhoneOption answerPhoneOption = voiceOption.getAnswerPhoneOption()
.orElseThrow(() -> new RuntimeException("Anrufbeantworteroption nicht gefunden"));
return new AnswerPhoneOptionCancellation(
mobileLine.getId(),
voiceOption.getId(),
answerPhoneOption.getId()
);
}
// 4.Beziehen Sie sich auf den Gesamtwert aller vertraglich vereinbarten monatlichen Nutzungsgebühren
public MonthlyFee totalMonthlyFee(UserId userId) {
Optional<MobileLine> mobileLineOptional = repository.findMobileLine(userId);
MobileLine mobileLine = mobileLineOptional
.orElseThrow(() -> new RuntimeException("Mobilfunkleitung nicht gefunden"));
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();
}
}
}
Sie sind fertig. (Sie können den Code etwas mehr in "MobileLine" verschieben, aber ich habe ihn in den Dienst geschrieben, um das Verständnis des gesamten Bilds des Prozesses zu erleichtern.)
Ich glaube nicht, dass es viele Leute gibt, die das sehen und "wunderbaren Code" fühlen! Einige Leute sind jedoch möglicherweise nicht in der Lage herauszufinden, was falsch ist und was zu tun ist.
Von hier aus möchte ich diesen Code verbessern, während ich das Thema interpretiere und den Code erkläre.
Es ist ein kleiner Sprung, aber ich schreibe seit 1-2 Jahren Code und habe mich in letzter Zeit stark gefühlt.
Es gibt drei Arten von "Nein"! Das ist. Lassen Sie uns über jedes mit dem obigen Thema und Code nachdenken.
Das erste ist das.
In diesem Beispiel die Mobilfunkleitung in "1. Überprüfen Sie, ob Sie eine Mobilfunkleitung beantragen können" und Die Sprachoption und die Anrufbeantworteroption "4. Beziehen Sie sich auf den Gesamtwert aller vertraglich vereinbarten monatlichen Nutzungsgebühren".
Wenn diese nicht vorhanden sind, werden sie als ** ein Muster des normalen Systems ** behandelt. Grundsätzlich ist dieser Fall ** keine Ausnahme **.
Dieser Fall entspricht ** einem Fehlermuster **, wenn es dem obigen Satz entspricht. Wenn Sie jedoch einen Fehler in einem Wort sagen, ist er nicht von dem später beschriebenen dritten Typ zu unterscheiden, sodass wir ihn als ** Geschäftsfehler ** im Sinne eines ** Fehlers bezeichnen, der in den Service-Spezifikationen erwartet wird **.
Möglicherweise liegt ein Servicefehler vor. Kurz gesagt, ich finde es gut, wenn es bei Ihren Verwandten keine Unstimmigkeiten gibt. Ich nenne es einen Geschäftsfehler, weil es ein Fehler ist, der von der Geschäftslogik erkannt werden sollte.
Dieser Fehler schreibt Logik zum Erkennen und Schützen. Ich kann nicht sicher sagen, weil es vom Design abhängen kann, aber es gibt in diesem Fall keine Ausnahmen. ** **.
In diesem Beispiel entspricht die Anrufbeantworteroption "2. Überprüfen Sie, ob Sie die Anrufbeantworteroption abbrechen können". Wenn es einen Anrufbeantworter gibt, ist dieser "normal", und wenn dies nicht der Fall ist, ist dies "einer der vielen anderen Gründe, warum Sie nicht stornieren können".
Der Unterschied ist der Unterschied in der nachfolgenden Verarbeitung.
Da A ein normales System mit oder ohne ist, gibt es keinen Unterschied im Durchfluss. Wenn andererseits B vorhanden ist, machen Sie XX, wenn nicht, machen Sie stattdessen XX, oder wenn nicht, fahren Sie mit ~ ~ fort und so weiter.
B ist ein Bild der Verzweigung mit "Ja / Nein" im Flussdiagramm.
Der letzte ist ** ein Fehler **, der in den Service-Spezifikationen nicht angenommen (definiert) wird. Ich nenne es ** Systemfehler **, weil dies hauptsächlich aus Systemgründen geschieht.
Beispielsweise schlägt die DB-Verbindung fehl, der externe Systemaufruf läuft ab oder der Wert, der in der DB hätte beschädigt werden sollen, kann nicht referenziert werden. Es kommt zu Datenbeschädigung / -verlust aufgrund doppelter Übermittlung oder fehlerhafter Operation.
Kurz gesagt, es handelt sich um einen Fehler, der nicht auftritt, wenn der Dienst normal funktioniert und wie erwartet verwendet wird.
In diesem Fall wäre ** mit Ausnahmen ** angemessen. Ich fange es nicht eins nach dem anderen.
In diesem Beispiel die mobile Leitung von "2. Überprüfen Sie, ob Sie die Anrufbeantworteroption stornieren können". Optionen für mobile Leitungen und Sprache sowie Anrufbeantworteroptionen in "3. Holen Sie sich die Elemente, die zum Abbrechen der Anrufbeantworteroption erforderlich sind", Die Mobilfunkleitung in "4. Beziehen Sie sich auf den Gesamtwert aller vertraglich vereinbarten monatlichen Nutzungsgebühren" ist gleichwertig.
In 2. gibt es beispielsweise eine Mobilfunkleitung (thematisch), sodass es unerwartet ist, dass es keine solche Leitung gibt. Ich weiß nicht was passiert ist. (Eine bestimmte Voraussetzung ist beispielsweise ein Prozess, der von einem Bildschirm aus aufgerufen wird und nur an eine Person mit einer Mobilfunkleitung übertragen werden kann. Das ist nicht ungewöhnlich, oder?)
Im Gegensatz zu B erscheint C nicht im Flussdiagramm mit "Ja / Nein".
Zum Beispiel ist "Ja / Nein-Statusinkonsistenz aufgetreten" oder "Ja / Nein-Verbindung zur Datenbank fehlgeschlagen" im Flussdiagramm der Dienstspezifikation nicht angegeben, oder?
Im Gegensatz zu B ist C häufig nicht in der Lage, eine nachfolgende Verarbeitung durchzuführen. Es ist nicht sinnvoll, nach Sprachoptionen zu suchen, wenn Sie Ihre Mobilfunkleitung nicht durchsuchen können, oder? In vielen Fällen wird in diesem Fall nichts passieren.
In anderen Fällen wird B reproduziert, C jedoch nicht.
B sagt zum Beispiel: "Wenn Sie nicht über die Anrufbeantworteroption verfügen, können Sie überprüfen, wie oft Sie stornieren können ** und Sie erhalten das gleiche Ergebnis **". C hat möglicherweise etwas wie "Ich wurde angegriffen und konnte aufgrund hoher Auslastung keine Verbindung zur Datenbank herstellen, aber ich habe mich damit befasst ** und war erfolgreich **, als ich mich erneut bewarb."
Selbst wenn ich einfach "Vorhandensein oder Nichtvorhandensein einer Sprachoption" sage, habe ich festgestellt, dass die Handhabung zu diesem Zeitpunkt anders ist.
Dieses Mal möchte ich den Sprung wagen und die Politik verfolgen, "jedes Mal eine andere Klasse zu bilden".
Ich werde versuchen, die notwendigen Informationen als "Nein" zu organisieren.
Es werden keine Berechnungen durchgeführt, es reicht also zu wissen, ob es wirklich existiert.
Auch hier gibt es keine spezielle Berechnung, daher scheint es keinen erforderlichen Wert zu geben.
Unter der Annahme, dass alles hier ist, benötigen wir die "ID" aller Elemente.
Hier ist es normal, auch wenn es nicht unter der Mobilfunkleitung liegt, und in einigen Fällen ist der Preis des Elements erforderlich.
Wenn ich es so betrachte, ist die erste von mir erstellte "MobileLine" tatsächlich wie eine Summe von vier Anforderungen geformt.
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;
}
Es stellt sich jedoch heraus, dass "ID" und monatliche Gebühren weniger wahrscheinlich sind und dass "VoiceOption" nicht immer "optional" ist.
Wenn Sie alle Elemente haben und sie unter Berücksichtigung der Möglichkeit, dass sie nicht vorhanden sind, auf "Optional" setzen, können Sie natürlich alle Anforderungen erfüllen. In Wirklichkeit hat dies jedoch nicht viel Verdienst und es nimmt nur an Schärfe zu. (Ich werde das am Ende ansprechen)
Daher werde ich von hier aus "eine Klasse vorstellen, die sich bis zur letzten Minute auf die minimalen Elemente konzentriert".
Die Anzahl der Klassen wird sehr groß sein, aber ich habe beschlossen, den Sprung zu wagen, also werde ich eine große Anzahl von Klassen erstellen.
Keine Klasse erforderlich. Das Vorhandensein oder Fehlen von "Boolean" ist ausreichend.
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 {
}
Mit den aktuellen Spezifikationen ist es möglich zu beurteilen, ob eine Anwendung aufgrund der Anwesenheit oder Abwesenheit erfolgen soll oder nicht, sodass überhaupt kein Wert vorliegt. (Es fühlt sich ein wenig nutzlos an, aber in Wirklichkeit benötigen Sie verschiedene Werte wie verschiedene Status und Jahre der Vertragsfortführung, und ich hoffe, dass Sie Ihr Gehirn mit solchen Wahnvorstellungen ergänzen können.)
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;
}
Dies hat eine andere Atmosphäre als zuvor. Ich habe eine ID, die nur für diese Anforderung benötigt wird, und alle optionalen IDs fehlen.
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);
}
Es gibt auch eine monatliche Gebühr, die nur für diese Anforderung benötigt wird, und ein untergeordnetes Element in "Optional".
Da sich die zurückgegebene Klasse geändert hat, ändert sich auch die Methode im Repository.
MobileLineRepository.java
public interface MobileLineRepository {
boolean isMobileLineApplicable(UserId userId); //Keine Notwendigkeit, die Klasse zurückzubekommen
MobileLineForAnswerPhoneCancellableCheck findForCancellable(UserId userId);
MobileLineForAnswerPhoneCancellation findForCancel(UserId userId);
MobileLineForTotalMonthlyFee findForTotalMonthlyFee(UserId userId);
}
Das "Optional" ist weg!
Wo ist das "Optional" von hier verschwunden, zum Beispiel "MobileLineForAnswerPhoneCancellation.java"? Anstatt irgendwohin zu ziehen, ist es wirklich weg.
Wenn in der Implementierungsklasse von "Repository" eine Statusinkonsistenz auftritt, ist dies eine Ausnahme. Der Teil, der ".orElseThrow ()" in der Serviceschicht ausgeführt hat, wird auf Inkonsistenz überprüft, bevor er in "Optional" eingeschlossen wird. Wenn dies auftritt, ist dies eine Ausnahme.
Sie müssen ohnehin in der Lage sein, "get ()" später zu erhalten. Machen wir also eine Ausnahme, wenn Sie feststellen, dass dies nicht möglich ist. Auf diese Weise müssen Sie es nicht als "Optional" in "Domain" behalten und später überprüfen.
Es ist einfach, wenn Sie das alles haben. Ich denke sogar, dass der einfachste in erster Linie "Service" ist.
Alles was Sie tun müssen, ist die "Domain" zu verwenden, die Sie bisher erstellt haben.
MobileLineService.java
public class MobileLineService {
private MobileLineRepository repository;
// 1.Prüfen Sie, ob Sie eine Mobilfunkleitung beantragen können
public boolean checkMobileLineApplicable(UserId userId) {
return repository.isMobileLineApplicable(userId);
}
// 2.Überprüfen Sie, ob Sie die Anrufbeantworteroption abbrechen können
public boolean checkAnswerPhoneCancellable(UserId userId) {
MobileLineForAnswerPhoneCancellableCheck mobileLine = repository.findForCancellable(userId);
return mobileLine.getVoiceOption().map(it -> it.getAnswerPhoneOption().isPresent()).orElse(false); //Ein wenig Einfallsreichtum mit Karte oder sonst
}
// 3.Holen Sie sich die Elemente, die zum Abbrechen der Anrufbeantworteroption erforderlich sind
public AnswerPhoneOptionCancellation cancelAnswerPhoneOption(UserId userId) {
MobileLineForAnswerPhoneCancellation mobileLine = repository.findForCancel(userId);
return new AnswerPhoneOptionCancellation(
mobileLine.getId(), //IsPresent muss nicht überprüft werden
mobileLine.getVoiceOption().getId(), //Es wird bestätigt, dass ein Wert vorhanden ist, da das Repository keine Ausnahme ausgelöst hat
mobileLine.getVoiceOption().getAnswerPhoneOption().getId()
);
}
// 4.Beziehen Sie sich auf den Gesamtwert aller vertraglich vereinbarten monatlichen Nutzungsgebühren
public MonthlyFee totalMonthlyFee(UserId userId) {
MobileLineForTotalMonthlyFee mobileLine = repository.findForTotalMonthlyFee(userId);
return MonthlyFee.sum(
mobileLine.getFee(), //Ein wenig Einfallsreichtum mit Karte oder sonst
mobileLine.getVoiceOption().map(it -> it.getFee()).orElse(MonthlyFee.zero()), //Es kann etwas einfacher sein, wenn Sie es gehorsam als 0 Yen hinzufügen
mobileLine.getVoiceOption().flatMap(it -> it.getAnswerPhoneOption()).map(it -> it.getFee()).orElse(MonthlyFee.zero())
);
}
}
Der große Unterschied zum ersten "MobileLineService" besteht darin, dass nicht nach Systemfehlern gesucht wird. "Es sollte keine Elemente geben" glaubt an die Klasse, die vom "Repository" zurückgegeben wird.
Sie können sich also nur auf die Berechnungen konzentrieren, die für Ihre Anforderungen durchgeführt werden müssen.
Jetzt, wo ich so weit gekommen bin, möchte ich noch etwas reparieren.
Sie können sich also nur auf die Berechnungen konzentrieren, die für Ihre Anforderungen durchgeführt werden müssen.
Dies ist die Geschäftslogik. Sie können es sich als "die notwendigen Berechnungen zur Erfüllung der Service-Spezifikationen" vorstellen.
Sie möchten die Berechnung testen, oder? Du solltest wollen. Es ist unmöglich, es nicht zu tun, oder? Ja ich möchte.
Hier oder so!
return mobileLine.getVoiceOption().map(it -> it.getAnswerPhoneOption().isPresent()).orElse(false);
Hier oder 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())
);
Sind Sie sicher, dass Sie die Bereitstellung durchführen können, ohne den Betrieb zu überprüfen? Ist es absolut in Ordnung?
Wenn Sie alle Muster im "Service" -Test bestehen möchten, müssen Sie viele Rückgabewerte für "Repository" vorbereiten.
Es können verspottete oder registrierte Dummy-Daten in der Datenbank sein, aber das können Sie nicht.
Daher sollten wir die folgende Beschreibung in der Tabelle der Beschreibung der ersten Schicht beachten.
domain Beschreiben Sie die Geschäftslogik
Ich werde es nur einmal posten. Dies ist die letzte Verbesserung.
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 {
}
Fragen Sie die Stammklasse "Können Sie sich bewerben?"
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
);
}
}
Ich wagte den Sprung und beschloss, die Niststruktur zu stoppen und sie zu einer flachen Klasse zu machen. Ich brauche sie sowieso alle.
Ich sagte nur "eine Sammlung von Materialien für Informationen, die für die Stornierung notwendig sind". Wenn Sie der Stammklasse "Erstellen Sie die für die Stornierung erforderlichen Informationen" mitteilen, werden diese unter Verwendung der Materialien zusammengestellt. Sie können nicht sehen, was das Material speziell auf der Außenseite ist. Es ist gekapselt.
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;
}
}
Ich war hier ein wenig verwirrt, aber bereite eine Methode vor, die die Summe der Preise unter sich zurückgibt, und die Oberschicht wird sich selbst hinzufügen. Nun, die Details im Inneren sind eine triviale Angelegenheit, und auch hier müssen Sie der Stammklasse nur "totalisieren" sagen.
Ich denke, es ist besser, das Repository nach den Anforderungen der zurückzugebenden Klasse zu unterteilen. Es geschah, als ich mit der Paketorganisation fortfuhr. (In letzter Zeit bin ich immer noch nicht sehr zuversichtlich.)
Daher ist das Repository auch in so viele Klassen unterteilt wie die Anzahl der erstellten Klassen.
Ich werde den Code weglassen.
MobileLineService.java
public class MobileLineService {
private MobileLineRepository mobileLineRepository;
private MobileLineForAnswerPhoneCancellableCheckRepository forAnswerPhoneCancellableCheckRepository;
private MobileLineForAnswerPhoneCancellationRepository forAnswerPhoneCancellationRepository;
private MobileLineForTotalMonthlyFeeRepository totalMonthlyFeeRepository;
// 1.Prüfen Sie, ob Sie eine Mobilfunkleitung beantragen können
public boolean checkMobileLineApplicable(UserId userId) {
return mobileLineRepository
.isMobileLineApplicable(userId);
}
// 2.Überprüfen Sie, ob Sie die Anrufbeantworteroption abbrechen können
public boolean checkAnswerPhoneCancellable(UserId userId) {
return forAnswerPhoneCancellableCheckRepository.find(userId)
.isAnswerPhoneCancellable();
}
// 3.Holen Sie sich die Elemente, die zum Abbrechen der Anrufbeantworteroption erforderlich sind
public AnswerPhoneOptionCancellation cancelAnswerPhoneOption(UserId userId) {
return forAnswerPhoneCancellationRepository.find(userId)
.cancel();
}
// 4.Beziehen Sie sich auf den Gesamtwert aller vertraglich vereinbarten monatlichen Nutzungsgebühren
public MonthlyFee totalMonthlyFee(UserId userId) {
return totalMonthlyFeeRepository.find(userId)
.getTotalMonthlyFee();
}
}
Sie können jetzt einfach die Stammklasse aus dem Repository anfordern und die Stammklasse auffordern, die Berechnung durchzuführen! !!
Das ist der einzige Service.
Die Berechnung und Verarbeitung wurden getrennt.
Der in die Domänenklasse verschobene Berechnungstest kann manuell durchgeführt werden, ohne dass Dummy-Daten in die Datenbank eingegeben werden müssen. Sie können direkt unter der Stammklasse "neu" und so viele testen, wie Sie möchten.
Ich habe den Code dreimal gepostet.
domain
: Ich habe alles und betrachte allesRepository
: Nur ein Accessor für Domain
service
: Systemfehlerprüfung, -verarbeitung, -berechnungdomain
: Spezialisiert auf Anforderungen und hat die erforderlichen Werte in der richtigen FormRepository
: Systemfehlerprüfungservice
: Verarbeitung, Berechnungdomain
: BerechnungRepository
: SystemfehlerprüfungService
: VerarbeitungWenn Sie es so betrachten, können Sie sehen, dass die Verantwortlichkeiten allmählich vom Dienst getrennt und in die Domäne verschoben wurden. Oder besser gesagt, der erste Dienst war schließlich zu schwer.
Betrachten wir es aus einer etwas anderen Perspektive
get ()
, orElseThrow ()
get ()
, orElseThrow ()
verschwindet@ Getter
ist wegIn der Regel sollte "Optional" nicht "get ()" sein.
(Ich werde die Details weglassen, aber im Grunde ist es besser, nach dem Zweitplatzierten nach get ()
nach einem Verbesserungsplan zu suchen.)
Als wir uns verbesserten, verschwanden "get ()" und "orElseThrow ()", so dass es keine Ausnahmen um "Optional" gab.
Durch das Befehlen der Root-Klasse ist @ Getter
verschwunden.
Dies bedeutet, dass die Kapselung erfolgreich war.
Die zuletzt erstellte Klasse ist nach Anforderungen getrennt. Selbst wenn nur für eine bestimmte Anforderung ein zusätzlicher Wert erforderlich ist, wirkt sich die Änderung nur auf diese Klasse aus. Die Serviceklasse sagt der Stammklasse nur "~~", ohne die darin enthaltenen Elemente zu kennen.
Selbst wenn das Antragsdatum zum Stornierungsurteil hinzugefügt wird, muss der Service nicht repariert werden, und eine erneute Bewertung ist nur im Falle einer Preisberechnung nicht erforderlich.
Es gibt keine Ausnahmen
Nicht als Zweig im Flussdiagramm der Dienstspezifikation angegeben
Es gibt keine Ausnahmen
Wird im Ablaufdiagramm der Dienstspezifikation als Zweig angegeben
Die nachfolgende Verarbeitung nach dem Auftreten wird ebenfalls normal gemäß den Spezifikationen verarbeitet
Da es in der Dienstspezifikation geschrieben ist, wird es von "Domäne" erkannt, die Geschäftslogik schreibt
Wenn der Datenstatus gleich ist, tritt immer der gleiche Geschäftsfehler auf
Verwenden Sie Ausnahmen
Nicht als Zweig im Flussdiagramm der Dienstspezifikation angegeben
In vielen Fällen kann die nachfolgende Verarbeitung nach ihrem Auftreten nicht fortgesetzt werden.
Da es nicht in der Dienstspezifikation geschrieben ist und Ausnahmen enthält, schützen Sie es mit "Mapper", anstatt es mit "Domain" zu erkennen, die Geschäftslogik schreibt.
Selbst wenn der Datenstatus derselbe ist, kann er aufgrund von Lade- oder Kommunikationsunterbrechungen auftreten oder nicht. (Wenn die Daten inkonsistent sind, werden sie immer auf die gleiche Weise angezeigt.)
Erstens wirkt sich das Ändern einer Anforderung bei speziellen Klassen nicht auf eine andere aus.
Die Summensatzklasse setzt alle Werte, die nur von bestimmten Anforderungen gehalten werden dürfen, auf "Optional". (Oder "Liste")
Dies ist beispielsweise "Das Stornierungsantragsdatum ist" leer ", außer beim Stornieren. Wenn Sie jedoch die Stornierung stornieren, sollte das Stornierungsantragsdatum einen Wert enthalten." Es wird ein "Optional" geben, das eine große Menge an Vorkenntnissen erfordert, wie "Es ist immer eine leere Liste unmittelbar nach der Bewerbung, aber beim Abbrechen sollte es eine Liste sein, die 1-3 Elemente enthält." (Oder "Liste") Das ist super scharf. (wahre Geschichte)
In Anbetracht der Anwendung, Änderung, Stornierung, Stornierung, optionalen Elemente usw. wird die Summensatzklasse mit "Optional" abgedeckt.
orElseThrow (" Es sollte einen Fall von ~ ~ geben ")
Können Sie sich nicht leicht vorstellen, abgedeckt zu werden?
Ich denke nicht, dass Ausnahmen in "Domain" gemacht werden sollten.
Da es sich um eine Berechnung im Zusammenhang mit "Domain" handelt, kommt eine Zustandsinkonsistenz nicht in Frage. Dies liegt daran, dass ich denke, dass der Wert den Fehler ausdrücken sollte, da der Fehler erwartet wird, solange er als Geschäftslogik berechnet wird.
Lassen Sie uns alle Ausnahmen auf "Mapper" verschieben. (Leicht gewalttätig)
Was ist mit "Service"? "Service" kann für "Fang" geeignet sein. Beispiel: "Wenn ein Systemfehler vorliegt, wird ein Alarm ausgegeben."
Diesmal gab es nur eine Routenklasse, aber zum Beispiel Wie "Ich habe einen Vertrag für eine Anrufbeantworteroption" & "Es gibt keinen unbezahlten Betrag für eine Anrufbeantworteroption" & "Zwei Jahre sind seit dem Vertrag vergangen" Andere Repositorys und Stammklassen werden ebenfalls angezeigt, wenn es sich um eine zusammengesetzte Bedingung handelt.
Ich denke, es liegt in der Verantwortung der Service-Schicht, diese und große Domänen zu verwalten, für die alle diese Domänen erforderlich sind. jetzt.
Ist es so etwas wie eine Klasse, die die meisten spezifischen Berechnungen und Systemfehler, die das Ende berücksichtigen sollte, nicht kennt, sondern nur die "Verarbeitungsreihenfolge" und die "ganzen Zeichen" kennt?
Im ersten Beispiel mussten Dummy-Daten der Datenbank eingegeben werden, um die wesentlichen Muster der Berechnungslogik abzudecken.
Im letzten Beispiel wurde die Berechnung in "Domäne" unterteilt, sodass Sie "Neu" und mit einem beliebigen Wert testen können.
Ich schrieb am Anfang:
Es ist nur eine "Klassifizierung, die ich an dieser Stelle in Betracht gezogen habe", aber sie macht den Verantwortungsbereich für jedes Paket klar. Wir glauben auch, dass es sich natürlich einem Design annähert, das ordnungsgemäße Unit-Tests ermöglicht und die Organisation und Wartung der Logik erleichtert.
Eigentlich habe ich zu diesem Zeitpunkt nicht viel nachgedacht, also dachte ich, ich würde es löschen, wenn ich keine gute Verbindung herstellen könnte, aber ich war erleichtert, dass es unerwartet verbunden war.
Systemfehler, Verarbeitung und Berechnungen befinden sich jetzt in jeder Schicht, können in Einheiten getestet werden und Änderungen der Anforderungen wirken sich nicht mehr auf andere Anforderungen aus!
das ist alles.
Könnten Sie vorstellen, dass es eine Menge Verdienste gibt, wenn Sie nur "nichts" in Betracht ziehen?
Optional
ist praktisch und vor allem liebe ich den Fehlertyp Soyu, aber ich mache nichts unangemessen Optional
Ich denke, dass es im Vergleich zu den Anforderungen natürlich einem guten Design näher kommt.
Recommended Posts