DDD - Implementierung des Domänendienstes: Domäne oder Infrastruktur

Sep 22 2020

Ich habe dieses klassische DDD-Problem. Ich habe einen Domain-Service "DetectPriority", der einige Dinge erledigt.

PM bitte mich, 2 verschiedene Dienste zu erstellen; ein INTERNES (mit ist voll von Geschäftsregeln und beinhaltet viele andere Domain-Modelle) und ein anderes ETERNAL (ein einfacher API-Aufruf).

Innerhalb der Domäne gibt es eine Schnittstelle "DetectPriorityInterface". Beide Implementierungen MÜSSEN gleichzeitig aktiv sein. Eine Art "gemischt" muss in Echtzeit eine anstelle der anderen auswählen.

Das Problem ist: Wo sollen diese Implementierungen (zwei Implementierungen) leben: in der Domänenschicht oder in der Infrastrukturschicht?

Die interne Implementierung ist voll von Geschäftsregeln und sollte sich in der Domänenschicht befinden. Die externe Implementierung ist ein einfacher Aufruf und sollte in der Infrastruktur erfolgen.

Sollten wir beide in die Infrastrukturschicht einfügen?

Vielen Dank

BEARBEITEN

Tatsächlich haben wir eine Schnittstelle "DetectPriority" und drei Implementierungen, ALLE in unserer Domänenschicht (temporäre "falsche" Lösung):

  1. InternalDetector (mit Geschäftsregeln)
  2. ExternalDetector (Mit externem API-Aufruf)
  3. MixerDetector (beide Implementierungen abrufen und die Auswahl treffen)

Clients verwenden die Schnittstelle, sodass für die Anwendungsschicht alle diese Elemente transparent sind. Im nächsten Schritt entfernen wir den internen (oder externen) und den Mixer und verwenden nur eine Implementierung. (Die Idee hinter all diesen ist zu verstehen, wer besser abschneidet, es ist ein A / BN-Test)

Unsere interne Debatte lautet: Weil InternalDetector einige Domänenregeln hat, die NUR zu diesem Detektor gehören, sollte er in der Infrastrukturschicht leben, da es sich nicht um allgemeine Domänenregeln handelt. Einige von uns sind damit nicht einverstanden, da wir in InternalDetector nur Geschäftsregeln haben und dies in Infra Layer nicht sehen.

Wahrscheinlich sollte der richtige Weg darin bestehen, Intern in Domäne und Extern in Infra hinzuzufügen. Aber es scheint ein bisschen verwirrt zu sein.

Das Zusammenfügen aller Elemente in der Infra-Ebene wäre für Entwickler besser lesbar ...

Wir haben einige Dinge in Büchern nicht gefunden, da Sie normalerweise eine einzige Implementierung eines Domain-Dienstes haben.

Antworten

1 AndrewPrigorshnev Sep 23 2020 at 18:47

Die kurze Antwort lautet, dass Sie einen internen Dienst in der Domänenschicht und einen externen in der Infrastrukturschicht implementieren sollten, genau wie Sie es in Ihrer Frage gesagt haben. Auf diese Weise wird alles an seinem Platz sein.

Ein weiterer wichtiger Punkt ist, dass sich der Code, der entscheidet, welcher Dienst aufgerufen werden soll, auch in der Domänenschicht befindet . Wie ich mir aus Ihrer Frage vorstellen kann, entscheiden Sie anhand einer Geschäftsregel, welchen Detektor Sie verwenden möchten. Die Tatsache, dass ein Detektor in Ihrer Anwendung implementiert ist und ein anderer nicht nur ein Implementierungsdetail Ihres Systems ist. Tatsächlich entscheiden Sie sich einfach dafür, einen Satz von Geschäftsregeln oder einen anderen unter bestimmten Bedingungen zu verwenden. Es ist eine Geschäftsentscheidung .

Bei DDD geht es ziemlich oft um schwierige Kompromisse. Wenn Sie jedoch nach einem guten Kompromiss suchen, würde ich empfehlen , die Domänenlogik niemals außerhalb der Domänenschicht zu verschieben und niemals Referenzen von der Domänenschicht zu anderen hinzuzufügen.

Das ist wichtig.

Und hier ist ein Beispiel, wie Sie diese Aufgabe lösen können, ohne diese Regeln zu verletzen:

// Names in this code should be changed to something with business 
// meaning. For example `externalDetector` can be `governmentDetector` 
// and `internalDetector` can be `corporateDetector`.

// Declare a service interface in the domain layer
public interface DetectPriority {}


// Inject both detectors in the domain service.
// Your dependency injection code should inject here 
// an internal implementation and an external one, 
// implemented in the infrastructure layer.
// So your DI code knows about different implementations
// but the domain service doesn't.
// For the domain service it's just two implementations 
// of domain interface IDetector
IDetector _externalDetector;
IDetector _internalDetector;


// Implement the method of the domain service like this:
public Priority Detect() 
{
    if (weShouldUseExternalSetOfRules) 
    {
        return _externalDetector.Detect(); // this one is implemented in your infrastructure layer
    }
    else 
    {
        return _internalDetector.Detect(); // this one is implemented in your domain
    }
}

In dieser Lösung können Sie Folgendes sehen:

  1. Die gesamte Domänenlogik (eine Implementierung des internen Detektors und die Entscheidung, welche Regeln verwendet werden sollen) wird in der Domänenschicht platziert.
  2. Wir haben keine Verweise auf die Infrastrukturschicht aus unserer Domäne. Der Domänendienst hat nur den Verweis auf die IDetectorSchnittstelle, diese Schnittstelle wird jedoch in der Domänenschicht deklariert.
  3. In der Domänenschicht befindet sich kein Infrastrukturcode. In diesem Fall bedeutet Infrastrukturcode so etwas wie "Rufen Sie die GET-Methode dieses REST-Dienstes unter Verwendung dieses Parametersatzes in der Abfragezeichenfolge auf". Offensichtlich wird dieser Code in der externalDetectorImplementierung sein.

Um sicherzugehen, dass dies ein guter Weg ist, können Sie sich dieses Repository mit einer DDD-Beispielanwendung aus dem berühmten Buch von Eric Evans ansehen . Dort finden Sie eine in der Domänenschicht deklarierte Dienstschnittstelle und den in der Infrastrukturschicht selbst implementierten Dienst . Leider gibt es in dieser Anwendung keine Beispiele für die Verwendung dieser Dienstschnittstelle innerhalb der Domänenschicht. Es wird jedoch innerhalb der Domänenschicht deklariert, um eine solche Verwendung zu ermöglichen.

Und Sie können den gleichen Ansatz mit einer guten Erklärung in diesem großartigen Artikel finden .


BEARBEITEN

Nach neuen Informationen in der Frage ist die Auswahl eines Detektors die Entscheidung auf Anwendungsebene, wenn es um A / B-Tests geht. Alle anderen Dinge bleiben gleich. So:

  • MixerDetector sollte in der Anwendungsschicht sein
  • DetectPriority Schnittstelle - in der Domänenschicht
  • InternalDetector in der Domänenschicht
  • ExternalDetector in der Infrastrukturschicht

Und Sie brauchen dann keine "Geschäfts" -Namen für Ihre Detektoren, weil sie buchstäblich InternalDetectorund sind ExternalDetector.

VoiceOfUnreason Sep 22 2020 at 20:49

Sollten wir beide in die Infrastrukturschicht einfügen?

Normalerweise nicht, nein. Dies wird unter anderem Ihre Abhängigkeitsdiagramme durcheinander bringen. Wir möchten nicht, dass der Domänencode vom Infrastrukturcode abhängig ist (eine der Beweggründe für ein Domänenmodell besteht darin, dass Sie die Logik der Domäne implementieren können, ohne eng an den Kontext gekoppelt zu sein, in dem das Domänenmodell ausgeführt wird - Einführung Infrastrukturabhängigkeiten widersprechen diesem Ziel.

Das bedeutet nicht unbedingt, dass der Infrastrukturcode "weit weg" ist - siehe Paket für Feature vs. Paket für Schicht . Es handelt sich um unterschiedliche Verantwortlichkeiten (im Sinne des Einzelverantwortungsprinzips), daher besteht normalerweise eine gewisse Trennung zwischen den beiden.

Ein Aspekt, bei dem die beiden sehr unterschiedlich sind: Fehlermodi - Robuster Code, der über das Netzwerk kommuniziert, muss die Tatsache berücksichtigen, dass das Netzwerk unzuverlässig ist, aber das ist kein Domänenproblem , sodass wir unsere Domäne normalerweise nicht verschmutzen möchten Code mit Netzwerkkontingenzlogik.

Aber wenn Sie möchten, ignorieren Sie all das - die eigentliche Heuristik ist einfach: Wir möchten die Anordnung von Code, die über die gesamte Lebensdauer am einfachsten zu pflegen ist . Wenn dies in Ihrem Kontext bedeutet, dass Domänencode in die Infrastrukturebene eingefügt wird, sollten Sie dies tun.

Die Richtlinien von DDD und anderen Stilen sollen Ihnen in erster Linie dabei helfen, die Falle einer Erhöhung der Wartungskosten auf Lebenszeit zu vermeiden, indem Sie sich entscheiden, das zu tun, was "jetzt" einfach ist.

afh Sep 23 2020 at 11:16

Ich verwende, um Domänendienstimplementierungen beizubehalten, die frei von Infrastrukturabhängigkeiten in der Domänenschicht sind. Implementierungen einer Domänendienstschnittstelle, die Infrastrukturabhängigkeiten erfordern, sollten sich in der Infrastrukturschicht befinden.

In Ihrem Fall müssen Sie auch berücksichtigen, dass sich der Code, der die konkrete Implementierung Ihres DetectPriorityInterface zur Laufzeit instanziiert , in der Infrastrukturschicht befinden muss und auch direkt vom externen Domänendienst abhängig ist .

Ich schlage vor, Sie haben eine Fabrik für diesen Job, die entscheidet, den einen oder anderen Domänendienst basierend auf einer Art Parameter zu erstellen. Sie können jedoch weiterhin eine Factory-Schnittstelle verwenden, die Sie in Ihre Domain-Ebene einfügen können. Nennen wir es PriorityDetectorFactoryInterface oder ähnliches. Und nur die konkrete Implementierung dieser Factory, nennen wir sie PriorityDetectorFactory, würde sich in der Infrastrukturschicht befinden.

Wenn Sie einen Anwendungsdienst haben, der für die Behandlung des Anwendungsfalls verantwortlich ist, in dem die Prioritätserkennung ins Spiel kommt, übergeben Sie das PriorityDetectorFactoryInterface an diesen Anwendungsdienst. Zur Laufzeit wird die konkrete Implementierung der Factory-Schnittstelle (dh PriorityDetectorFactory) in den Anwendungsdienst eingefügt. Damit können Sie auch die Anwendungsschicht beibehalten, in der Sie normalerweise nur die Workflows für die Orchestrierung Ihrer Anwendungsfälle ohne Infrastrukturabhängigkeiten definieren.

Damit hätten Sie:

  • DetectPriorityInterface in Ihrer Domänenschicht
  • InternalPriorityDetector (Implementierung von DetectPriorityInterface) in Ihrer Domänenschicht
  • ExternalPriorityDetector (Implementierung von DetectPriorityInterface) in Ihrer Infrastrukturschicht
  • PriorityDetectorFactoryInterface in Ihrer Domain Schicht und
  • PriorityDetectorFactory (Implementierung von PriorityDetectorFactoryInterface) in Ihrer Infrastrukturschicht
  • ... und der erwähnte Anwendungsdienst , der Ihren Anwendungsfall in Ihrer Anwendungsschicht behandelt

Hinweis: Dies alles basiert auf der Annahme, dass Ihre interne Domain-Service-Implementierung wirklich frei von anderen Abhängigkeiten als dem Material aus der Domain-Schicht selbst ist.