Nachdem ich "Implementieren von domänengesteuertem Design von Vaughn Vernon" gelesen habe, habe ich beschlossen, meinen Code für eine bessere Wiederverwendbarkeit umzugestalten, indem ich das, was ich für Kerndomänenkonzepte halte, in separate Module isoliere.
Jedes Modul enthält einen eigenen Satz unterschiedlicher Architekturebenen, darunter die Domänen-, Infrastruktur- und die Anwendungs- / Präsentationsschicht (gemäß Vaughns Empfehlung habe ich beschlossen, die Verantwortlichkeiten der Anwendungsschicht weiter von Routen, MVC-Controllern + Vorlagen zu trennen, die in der Präsentationsfolie).
Ich habe beschlossen, jede dieser Ebenen in einem eigenen Paket zu platzieren. und jedes Paket verweist auf die darunter liegende Ebene als Abhängigkeit. Das heißt: Die Präsentationsschicht hängt von der Anwendungsschicht ab, die Anwendung hängt von der Infrastruktur ab usw. Da das Repository Teil der Domäne ist, ist jede Repository-Schnittstelle innerhalb der Domänenschicht / des Domänenpakets vorhanden, wobei die Implementierung in der Verantwortung der Infrastrukturschicht / des Infrastrukturpakets liegt (Doctrine) , usw).
Ich hoffe, dass ich durch die Umstrukturierung meines Codes auf diese Weise die Anwendungsschicht austauschen und meine Domain für mehrere Webanwendungen wiederverwenden kann.
Der Code sieht schließlich so aus, als würde er sich wieder formen. Was mich jedoch immer noch verwirrt, ist diese Unterscheidung zwischen Anwendungs-, Infrastruktur- und Domänendiensten.
Ein häufiges Beispiel für einen Domänendienst ist etwas, mit dem Sie Kennwörter hashen würden. Dies ist aus SRP-Sicht für mich sinnvoll, da sich die Benutzerentität nicht mit den vielen verschiedenen Hashing-Algorithmen befassen sollte, die zum Speichern der Anmeldeinformationen eines Benutzers verwendet werden können.
In diesem Sinne habe ich diesen neuen Domain-Service genauso behandelt wie meine Repositorys. indem Sie eine Schnittstelle in der Domäne definieren und die Implementierung der Infrastrukturschicht überlassen. Jetzt frage ich mich jedoch, was mit den Anwendungsdiensten geschehen soll.
Derzeit verfügt jede Entität über einen eigenen Anwendungsdienst, dh die Benutzerentität verfügt über einen UserService innerhalb der Anwendungsschicht. Der UserService ist in diesem Fall für das Parsen primitiver Datentypen und die Behandlung eines allgemeinen Anwendungsfalls "UserService :: CreateUser (Stringname, String-E-Mail usw.): Benutzer verantwortlich.
Was mich betrifft, ist die Tatsache, dass ich diese Logik über mehrere Anwendungen hinweg erneut implementieren muss, wenn ich mich entscheide, die Anwendungsschicht auszutauschen. Ich denke, das führt mich zu meinen nächsten Fragen:
Sind Domänendienste lediglich eine Schnittstelle, die eine Abstraktionsebene zwischen der Infrastrukturschicht und Ihrem Modell bereitstellt? dh: Repositories + HashingServices usw.
Ich erwähnte einen Anwendungsdienst, der so aussieht:
Zugriff / Anwendung / Dienste / UserService :: CreateUser (Zeichenfolgenname, Zeichenfolgen-E-Mail usw.): Benutzer
Die Methodensignatur akzeptiert primitive Datentypargumente und gibt eine neue Benutzerentität zurück (kein DTO!).
Gehört dies in die Infrastrukturschicht als Implementierung einer in der Domänenschicht definierten Schnittstelle oder ist die Anwendungsschicht aufgrund primitiver Datentypargumente usw. tatsächlich besser geeignet ?
Beispiel:
Access/Domain/Services/UserServiceInterface
und
Access/Infrastructure/Services/UserService implements UserServiceInterface
Wie sollten separate Module mit unidirektionalen Beziehungen umgehen? Sollte Modul A die Anwendungsschicht von Modul B (wie jetzt) oder die Infrastrukturimplementierung (über eine separate Schnittstelle) referenzieren?
Benötigen Application Layer Services eine separate Schnittstelle? Wenn die Antwort ja lautet, wo sollten sie sich dann befinden?
Antworten:
Die Zuständigkeiten für Domänendienste umfassen mehrere Dinge. Am offensichtlichsten ist die Gehäuselogik, die nicht in eine einzelne Entität passt. Beispielsweise müssen Sie möglicherweise eine Rückerstattung für einen bestimmten Kauf autorisieren, aber um den Vorgang abzuschließen, benötigen Sie Daten von der
Purchase
Entität,Customer
Entität,CustomerMembership
Entität.Domänendienste stellen auch Vorgänge bereit, die von der Domäne benötigt werden, um ihre Funktionalität zu vervollständigen, wie z. B.
PasswordEncryptionService
, aber die Implementierung dieses Dienstes befindet sich in der Infrastrukturschicht, da es sich meistens um eine technische Lösung handelt.Infrastrukturdienste sind Dienste, bei denen es sich um einen Infrastrukturvorgang handelt, z. B. das Öffnen einer Netzwerkverbindung, das Kopieren von Dateien aus dem Dateisystem, das Gespräch mit einem externen Webdienst oder das Gespräch mit der Datenbank.
Anwendungsdienste sind die Implementierung eines Anwendungsfalls in der von Ihnen erstellten Anwendung. Wenn Sie eine Flugreservierung stornieren, würden Sie:
Die Anwendungsschicht ist der Client der Domäne. Die Domain hat keine Ahnung, was Ihr Anwendungsfall ist. Es macht die Funktionalität nur durch seine Aggregate und Domänendienste verfügbar. Die Anwendungsschicht spiegelt jedoch das wider, was Sie durch die Orchestrierung der Domänen- und Infrastrukturschicht erreichen möchten.
PHP ist möglicherweise nicht der beste Ort, um sich mit DDD vertraut zu machen, da viele der PHP-Frameworks (Laravel, Symfony, Zend usw.) dazu neigen, RAD zu fördern. Sie konzentrieren sich mehr auf CRUD und die Übersetzung von Formularen in Entitäten. CRUD! = DDD
Ihre Präsentationsschicht sollte dafür verantwortlich sein, die Formulareingaben aus dem Anforderungsobjekt zu lesen und den richtigen Anwendungsdienst aufzurufen. Der Anwendungsdienst erstellt den Benutzer und ruft das Benutzerrepository auf, um den neuen Benutzer zu speichern. Optional können Sie ein DTO des Benutzers an die Präsentationsebene zurückgeben.
Das Wortmodul in DDD-Jargon hat eine andere Bedeutung als das, was Sie beschreiben. Ein Modul sollte verwandte Konzepte enthalten. Ein Auftragsmodul in der Domänenschicht kann beispielsweise das Auftragsaggregat, die OrderItem-Entität, OrderRepositoryInterface und MaxOrderValidationService enthalten.
Ein Order-Modul in der Anwendungsschicht kann OrderApplicationServie, CreateOrderCommand und OrderDto enthalten.
Wenn Sie über Ebenen sprechen, sollte jede Ebene nach Möglichkeit vorzugsweise von Schnittstellen anderer Ebenen abhängen. Die Präsentationsschicht sollte von den Schnittstellen der Anwendungsschicht abhängen. Die Anwendungsschicht sollte auf Schnittstellen der Repositorys oder Domänendienste verweisen.
Ich persönlich erstelle keine Schnittstellen für Entitäten und Wertobjekte, da ich glaube, dass Schnittstellen mit einem Verhalten zusammenhängen sollten, aber YMMV :)
Es kommt darauf an :) Für komplexe Anwendungen baue ich Schnittstellen, da wir strenge Unit-, Integrations- und Abnahmetests anwenden. Eine lose Kopplung ist hier der Schlüssel und die Schnittstellen befinden sich in derselben Schicht (Anwendungsschicht).
Für einfache App baue ich direkt gegen die App-Dienste.
quelle
Hmm kurze Antwort auf eine lange Frage, aber ich sehe das Muster wie folgt
Regel 1: Domänenobjekte sollten einen einzelnen Aggregatstamm haben
Regel 2: Aggregierte Wurzeln sollten nicht zu groß sein. Teilen Sie die Dinge in begrenzte Kontexte auf
Problem: Aggregierte Wurzeln werden bald zu groß und es gibt keine klare Möglichkeit, eine Grenze zwischen den verschiedenen Domänenmodellen in ihnen zu ziehen
Lösung: Domänendienste. Erstellen Sie Schnittstellen, die Sie in Domänenmodelle einfügen können, damit diese Dinge außerhalb ihres Domänenkontexts oder ihres aggregierten Stamms ausführen können.
Ich würde also sagen, dass Ihre Beispiele nur normale Services / Repositorys usw., IDatabaseRepositoryForStoringUsers oder IGenericHashingCode sind
Ein Domänendienst ermöglicht die Kommunikation zwischen begrenzten Kontexten. dh
Wo sich Benutzer und Konten in getrennten aggregierten Wurzeln / begrenzten Kontexten befinden.
Wenn sich Benutzer und Konto im selben Aggregatstamm befinden, sollten Sie natürlich in der Lage sein:
Ich bin mir aus Ihrer Frage nicht ganz klar, wie Sie das nTier Application / Infrastructure-Zeug und das Module-Zeug integrieren. Sie möchten eigentlich keine Querverweise zwischen begrenzten Kontexten, daher würden Sie Ihre Domain-Service-Schnittstellen in ein eigenes Modul einfügen, das auf keinen anderen begrenzten Kontext verweist. Sie können nur Basiswerttypen oder nur DTOs verfügbar machen
quelle