Ich lerne DDD und denke darüber nach, in bestimmten Situationen Ausnahmen zu werfen. Ich verstehe, dass ein Objekt nicht in einen schlechten Zustand versetzt werden kann, daher sind hier die Ausnahmen in Ordnung, aber in vielen Beispielen werden auch Ausnahmen ausgelöst, wenn wir versuchen, einen neuen Benutzer mit vorhandener E-Mail in der Datenbank hinzuzufügen.
public function doIt(UserData $userData)
{
$user = $this->userRepository->byEmail($userData->email());
if ($user) {
throw new UserAlreadyExistsException();
}
$this->userRepository->add(
new User(
$userData->email(),
$userData->password()
)
);
}
Wenn also ein Benutzer mit dieser E-Mail vorhanden ist, können wir im Anwendungsdienst eine Ausnahme abfangen, aber wir sollten den Betrieb der Anwendung nicht mithilfe des Try-Catch-Blocks steuern.
Wie ist der beste Weg dafür?
Antworten:
Beginnen wir mit einem kurzen Überblick über den Problembereich: Eines der Grundprinzipien von DDD besteht darin, die Geschäftsregeln so nah wie möglich an den Stellen zu platzieren, an denen sie durchgesetzt werden müssen. Dies ist ein äußerst wichtiges Konzept, da es Ihr System "kohärenter" macht. Das Verschieben von Regeln "nach oben" ist im Allgemeinen ein Zeichen für ein anämisches Modell. Dabei handelt es sich bei den Objekten nur um Datenbeutel, und den Regeln werden die zu erzwingenden Daten hinzugefügt.
Ein anämisches Modell kann für Entwickler, die gerade erst mit DDD beginnen, sehr sinnvoll sein. Sie erstellen ein
User
Modell und ein ModellEmailMustBeUnqiueRule
, das mit den erforderlichen Informationen zur Validierung der E-Mail versehen wird. Einfach. Elegant. Das Problem ist, dass diese "Art" des Denkens grundsätzlich prozeduraler Natur ist. Nicht DDD. Was am Ende passiert, ist, dass Sie ein Modul mit Dutzenden vonRules
ordentlich verpackten und gekapselten Modulen haben , aber sie sind völlig kontextfrei, bis zu dem Punkt, an dem sie nicht mehr geändert werden können, da nicht klar ist, wann / wo sie erzwungen werden. Ist das sinnvoll? Es mag selbstverständlich sein, dass einEmailMustBeUnqiueRule
Wille auf die Schaffung eines angewendet wirdUser
, aber was ist mitUserIsInGoodStandingRule
? Langsam aber sicher erfolgt die Kornularisierung der Extraktion derRules
Wenn Sie nicht in ihrem Kontext stehen, haben Sie ein System, das schwer zu verstehen ist (und daher nicht geändert werden kann). Regeln sollten nur gekapselt werden, wenn das eigentliche Knirschen / Ausführen so ausführlich ist, dass Ihr Modell den Fokus verliert.Nun zu Ihrer spezifischen Frage: Das Problem mit dem
Service
/CommandHandler
throw theException
ist, dass Ihre Geschäftslogik beginnt ("nach oben") aus Ihrer Domain herauszulaufen. Warum muss IhreService
/CommandHandler
müssen eine E-Mail eindeutig sein? Die Anwendungsdienstschicht wird im Allgemeinen eher zur Koordination als zur Implementierung verwendet. Der Grund dafür kann einfach veranschaulicht werden, wenn wirChangeEmail
Ihrem System eine Methode / einen Befehl hinzufügen . Jetzt müssten BEIDE Methoden / Befehlshandler Ihre eindeutige Prüfung einschließen. Hier könnte ein Entwickler versucht sein, eine zu "extrahieren"EmailMustBeUniqueRule
. Wie oben erklärt, wollen wir diesen Weg nicht gehen.Einige zusätzliche Wissensverknappungen können uns zu einer DDD-Antwort führen. Die Eindeutigkeit einer E-Mail ist eine Invariante, die für eine Sammlung von
User
Objekten erzwungen werden muss . Gibt es in Ihrer Domain ein Konzept, das eine "Sammlung vonUser
Objekten" darstellt? Ich denke, Sie können wahrscheinlich sehen, wohin ich hier gehe.Für diesen speziellen Fall (und viele weitere, bei denen Invarianten über Sammlungen hinweg erzwungen werden) ist der beste Ort, um diese Logik zu implementieren, in Ihrem
Repository
. Dies ist besonders praktisch, da SieRepository
auch die zusätzliche Infrastruktur "kennen", die für die Ausführung dieser Art der Validierung erforderlich ist (den Datenspeicher). In Ihrem Fall würde ich diese Prüfung in dieadd
Methode einfügen. Das macht doch Sinn, oder? Konzeptionell ist es diese Methode,User
die Ihrem System wirklich etwas hinzufügt . Der Datenspeicher ist ein Implementierungsdetail.quelle
Moving rules "upwards" is generally a sign of an anemic model
? Aufwärts bedeutet für die Anwendungsschicht? oder zum Domain-Modell?Email
muss Ihr Modell ein Beutel mit Daten sein, der vor dem Senden auf Invarianten überprüft wird. Siehe: softwareengineering.stackexchange.com/questions/372338/…User
), oder akzeptieren, dass diese bestimmte Art von Invariante nicht ordnungsgemäß in Ihrer Domäne eingekapselt wird. Ersteres stellt eine Trennung von Daten und Verhalten dar, die zu einem prozeduralen Paradigma führt. Das ist weniger als ideal. Die Validierung sollte existiert mit Daten, nicht um Daten. Welchen Vorteil hat es außerdem, einen Domain-Service zu erstellen, der nur dazu dient, diese eine Regel zu überprüfen? Angeblich ist dieser Dienst ...Repository
,User
und diese Invariante und erreicht daher nicht die puristische Vision, auf die Sie sowieso drängen. Repository-Implementierungen sind Teil der Domäne und können daher bestimmte Verantwortlichkeiten erhalten.Sie können in jeder Ebene Ihrer Anwendung eine Validierung festlegen. Wo Sie festlegen müssen, welche Regeln von Ihrer Anwendung abhängen.
Beispielsweise implementieren Entitäten Methoden, die Geschäftsregeln darstellen, während Anwendungsfälle Regeln implementieren, die für Ihre Anwendung spezifisch sind.
Wenn Sie einen E-Mail-Dienst wie Google Mail erstellen, könnte man argumentieren, dass die Regel "Benutzer sollten eine eindeutige E-Mail-Adresse haben" eine Geschäftsregel ist.
Wenn Sie einen Registrierungsprozess für ein neues CRM-System erstellen, wird diese Regel wahrscheinlich am besten in einem Anwendungsfall implementiert, da diese Regel wahrscheinlich auch Teil Ihres Anwendungsfalls ist. Darüber hinaus ist das Testen möglicherweise auch einfacher, da Sie die Repository-Aufrufe in Ihren Anwendungsfalltests problemlos stubben können.
Aus Gründen der zusätzlichen Sicherheit können Sie diese Regel auch in Ihrem Repository durchsetzen.
quelle