DDD: Erstellen wiederverwendbarer Module und Diensttypunterscheidungen (Domäne, Infrastruktur, Anwendung)


8

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:

  1. Sind Domänendienste lediglich eine Schnittstelle, die eine Abstraktionsebene zwischen der Infrastrukturschicht und Ihrem Modell bereitstellt? dh: Repositories + HashingServices usw.

  2. 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
  3. 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?

  4. Benötigen Application Layer Services eine separate Schnittstelle? Wenn die Antwort ja lautet, wo sollten sie sich dann befinden?


2
Dies sind sehr unterschiedliche Themen. Ich schlage vor, Sie teilen dies in separate Fragen auf.
Guillaume31

Antworten:


7

Sind Domänendienste lediglich eine Schnittstelle, die eine Abstraktionsebene zwischen der Infrastrukturschicht und Ihrem Modell bereitstellt? dh: Repositories + HashingServices usw.

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 PurchaseEntität, CustomerEntität, CustomerMembershipEntitä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:

  1. Fragen Sie die Datenbank nach dem Reservierungsobjekt ab.
  2. Reservierung aufrufen-> Abbrechen.
  3. Objekt in DB speichern.

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.

Ich habe einen Anwendungsdienst erwähnt, der folgendermaßen aussieht: Access / Application / Services / UserService :: CreateUser (Zeichenfolgenname, Zeichenfolgen-E-Mail usw.): Benutzer Die Methodensignatur akzeptiert primitive Datentypargumente und gibt eine neue Benutzerentität zurück (kein DTO) !).

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.

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?

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 :)

Benötigen Application Layer Services eine separate Schnittstelle? Wenn die Antwort ja lautet, wo sollten sie sich dann befinden?

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.


Ich stimme für das meiste, was Sie geschrieben haben, zu, nur möchte ich sagen, dass der RAD-Teil der Frameworks so viel zum Prototyp der Anwendung beiträgt, dass wir uns zur Entkopplung auf die Domänenmodellierung verlassen können, auch wenn PHP damit nicht gut bekannt ist, wenn wir Haben Sie eine aktive Datensatzimplementierung als Datenquellenschicht? Wir können sie als DTO betrachten, da Onkel Bob sagte, dass dies eine andere Art von DTO ist und ein Adapter zwischen der Domänenschicht vermitteln kann. Die Datenquellenbefehle sind auch eine andere Art von DTOs. Die beste Vorgehensweise besteht darin, die zu koppeln Formulare zu Befehl (DTO) nicht die Entität, fast jedes Framework hat DI-Container, verwenden Sie also Schnittstellen.
Cherif BOUCHELAGHEM

1

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

User.UpgradeAccount(IAccountService accountService)
{
    accountService.UpgradeUsersAccount(this.Id);
}

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:

User.UpgradeAccount()
{
    this.MyAccount.Upgrade();
}

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


Die Integration ist ein Anliegen des Anwendungspakets / der Anwendungsschicht jedes Moduls. dh: Service Container Dynamic Bindings + Routen. Was modulübergreifende Beziehungen betrifft - wenn ein anderes getrenntes Modul (z. B. ProjectManagement) eine Authentifizierung innerhalb seiner jeweiligen API durchführen muss, füge ich die Anwendungsschicht des "Access" -Moduls als aufgelistete Abhängigkeit hinzu. Der Paketmanager ruft dann alle verbleibenden verschachtelten Abhängigkeiten ab. Zugriff auf Anwendung -> Zugriff auf Infrastrukturen -> Zugriff auf Domäne, damit ich die erforderlichen Arbeiten ausführen kann.
user2308097

1
Ja, ich denke, diese Art der Cross-Domain-Verknüpfung ist wahrscheinlich ein Fehler. Sie werden wahrscheinlich zirkuläre Abhängigkeiten bekommen
Ewan
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.