architecture diagram - Domain Driven Design:Domänenservice, Anwendungsdienst




rest book (6)

Kann jemand den Unterschied zwischen Domänen- und Anwendungsdiensten anhand einiger Beispiele erklären? Und wenn ein Dienst ein Domäne-Dienst ist, würde ich die tatsächliche Implementierung dieses Diensts innerhalb der Domäne-Assembly setzen, und wenn ja, würde ich auch Repositorys in diesem Domäne-Dienst einfügen? Einige Informationen wären sehr hilfreich.


Answers

Die beste Ressource, die mir geholfen hat, den Unterschied zwischen einem Anwendungsservice und einem Domain-Service zu verstehen, war die Java-Implementierung von Eric Evans Cargo-Beispiel, das here finden here . Wenn Sie es herunterladen, können Sie die Interna von RoutingService (ein Domain-Service) und der BookingService, CargoInspectionService (die Application Services sind).

Mein 'Aha'-Moment wurde von zwei Dingen ausgelöst:

  • Lesen Sie die Beschreibung der Dienstleistungen in dem obigen Link, genauer gesagt diesen Satz:

Domain-Services werden ausgedrückt durch die allgegenwärtige Sprache und die Domain-Typen, dh die Methodenargumente und die Rückgabewerte sind korrekte Domain-Klassen.

  • Lesen Sie diesen Blogeintrag , insbesondere diesen Teil:

Was ich bei der Trennung der Äpfel von den Orangen sehr hilfreich finde, ist der Anwendungs-Workflow. Alle Logik in Bezug auf den Anwendungsworkflow endet in der Regel darin, dass Anwendungsdienste in der Anwendungsschicht berücksichtigt werden, während Konzepte aus der Domäne, die nicht als Modellobjekte zu passen scheinen, letztendlich einen oder mehrere Domänendienste bilden.


Domänendienste: Methoden, die nicht wirklich zu einer einzelnen Entität passen oder Zugriff auf das Repository benötigen, sind in den Domänendiensten enthalten. Der Domänen-Service-Layer kann auch eigene Domänenlogik enthalten und ist genauso Teil des Domänenmodells wie Entitäten und Wertobjekte.

Anwendungsdienste: Der Anwendungsdienst ist eine dünne Schicht, die über dem Domänenmodell sitzt und die Anwendungsaktivität koordiniert. Es enthält keine Geschäftslogik und enthält nicht den Status von Entitäten. Es kann jedoch den Status einer Geschäftsworkflowtransaktion speichern. Sie verwenden einen Anwendungsdienst zum Bereitstellen einer API im Domänenmodell mithilfe des Nachrichten-Anforderungs-Antwort-Schemas.

Millett, C (2010). Professionelle ASP.NET Design Patterns. Wiley Verlag. 92.


Aus dem Red Book (Implementierung von Domain Driven Design, von Vaughn Vernon), verstehe ich die Konzepte so:

Domänenobjekte ( Entitäten und Wertobjekte ) kapseln das von der (Sub-) Domäne benötigte Verhalten ein und machen es so natürlich, ausdrucksstark und verständlich.

Domänenservices kapseln solche Verhaltensweisen ein, die nicht in ein einzelnes Domänenobjekt passen. Eine Book , die einem Client ein Book (mit entsprechenden Inventory ) zur Verfügung stellt, kann dies beispielsweise von einem Domänen-Service tun.

Anwendungsdienste wickeln den Fluss von Anwendungsfällen ab, einschließlich aller zusätzlichen Bedenken, die zusätzlich zu den Domänen benötigt werden. Es stellt solche Methoden häufig über seine API für den Verbrauch durch externe Clients zur Verfügung. Um auf unserem vorherigen Beispiel aufzubauen, kann unser Anwendungsdienst die Methode LendBookToClient(Guid bookGuid, Guid clientGuid) , die

  • Ruft den Client .
  • Bestätigt seine Berechtigungen. ( Beachten Sie, dass wir unser Domänenmodell frei von Sicherheits- / Benutzermanagementproblemen gehalten haben. Eine solche Verschmutzung könnte zu vielen Problemen führen. Stattdessen erfüllen wir diese technische Anforderung hier in unserem Anwendungsservice. )
  • Ruft das Book .
  • Ruft den Domänen-Service (Übergeben des Client und des Book ) auf, um die tatsächliche Domänenlogik des Ausleihens des Buchs an den Client zu verarbeiten. Zum Beispiel stelle ich mir vor, dass die Bestätigung der Verfügbarkeit des Buches definitiv Teil der Domänenlogik ist.

Ein Anwendungsdienst sollte im Allgemeinen einen sehr einfachen Ablauf haben. Komplexe Anwendungsdienstflüsse weisen oft darauf hin, dass die Domänenlogik aus der Domäne ausgelaufen ist.

Wie Sie hoffentlich sehen können, bleibt das Domain-Modell auf diese Weise sehr sauber und ist leicht zu verstehen und mit den Domain-Experten zu diskutieren, da es nur seine eigenen, tatsächlichen geschäftlichen Belange enthält. Auf der anderen Seite ist der Anwendungsfluss auch viel einfacher zu verwalten, da er von Domänenproblemen befreit ist und prägnant und unkompliziert wird.


Domain-Service ist die Erweiterung der Domain. Es sollte nur im Kontext der Domäne gesehen werden. Dies ist keine Benutzeraktion wie zum Beispiel ein Konto oder etwas ähnliches. Der Domänenservice passt dort, wo kein Status vorhanden ist. Andernfalls wäre es ein Domänenobjekt. Der Domain-Service macht etwas, das nur dann Sinn macht, wenn er mit anderen Mitarbeitern (Domain-Objekten oder anderen Services) gemacht wird. Und dieser Sinn liegt in der Verantwortung einer anderen Schicht.

Anwendungsdienst ist die Ebene, die die Interaktion zwischen den Domänenobjekten und Diensten initialisiert und überwacht. Der Ablauf ist im Allgemeinen wie folgt: Holen Sie ein Domain-Objekt (oder Objekte) aus dem Repository, führen Sie eine Aktion aus und legen Sie sie (dort) zurück (oder nicht). Es kann mehr tun - zum Beispiel kann es prüfen, ob ein Domain-Objekt existiert oder nicht, und Exceptions auslösen. So kann der Benutzer mit der Anwendung interagieren (und das ist wahrscheinlich der Ursprung des Namens) - indem Domänenobjekte und Dienste manipuliert werden. Anwendungsdienste sollten im Allgemeinen alle möglichen Anwendungsfälle darstellen . Wahrscheinlich das Beste, was Sie tun können, bevor Sie über die Domäne nachdenken, ist die Erstellung von Anwendungsdienstschnittstellen, was Ihnen einen viel besseren Einblick in das geben wird, was Sie wirklich tun wollen. Mit diesem Wissen können Sie sich auf die Domain konzentrieren.

Repositories können im Allgemeinen in Domain-Services eingefügt werden, aber dies ist eher selten Szenario. Es ist jedoch die Anwendungsebene, die es die meiste Zeit tut.


(Wenn Sie nicht lesen möchten, gibt es unten eine Zusammenfassung :-)

Ich habe auch mit der genauen Definition von Anwendungsdiensten gekämpft. Obwohl Vijays Antwort vor einem Monat sehr hilfreich für meinen Denkprozess war, bin ich mit einem Teil davon nicht einverstanden.

Andere Ressourcen

Es gibt sehr wenig Informationen über Anwendungsdienste. Themen wie Aggregatwurzeln, Repositories und Domain-Services werden ausführlich diskutiert, Anwendungsdienste werden jedoch nur kurz erwähnt oder ganz weggelassen.

Der MSDN Magazine-Artikel Einführung in das domänengestützte Design beschreibt Anwendungsdienste als eine Möglichkeit, Ihr Domänenmodell zu transformieren und / oder externen Clients zur Verfügung zu stellen, z. B. als WCF-Dienst. So beschreibt Vijay auch Anwendungsdienste. Aus dieser Sicht sind Anwendungsdienste eine Schnittstelle zu Ihrer Domäne .

Jeffrey Palermos Artikel über die Zwiebelarchitektur (Teil one , two und three ) sind eine gute Lektüre. Er behandelt Anwendungsdienste als Konzepte auf Anwendungsebene , z. B. die Sitzung eines Benutzers. Obwohl dies meinem Verständnis von Anwendungsdiensten näher ist, stimmt es noch nicht mit meinen Gedanken zu diesem Thema überein.

Meine Gedanken

Ich denke, Anwendungsdienste sind Abhängigkeiten, die von der Anwendung bereitgestellt werden . In diesem Fall könnte die Anwendung eine Desktopanwendung oder ein WCF-Dienst sein.

Domain

Zeit für ein Beispiel. Sie beginnen mit Ihrer Domain. Alle Entitäten und alle Domänendienste, die nicht von externen Ressourcen abhängig sind, werden hier implementiert. Alle Domänenkonzepte, die von externen Ressourcen abhängen, werden von einer Schnittstelle definiert. Hier ist eine mögliche Lösung Layout (Projektname in Fettdruck):

My Solution
- My.Product.Core (My.Product.dll)
  - DomainServices
      IExchangeRateService
    Product
    ProductFactory
    IProductRepository

Die ProductFactory und ProductFactory Klassen wurden in der ProductFactory implementiert. Das IProductRepository wird wahrscheinlich von einer Datenbank unterstützt. Die Implementierung ist nicht das Anliegen der Domäne und wird daher durch eine Schnittstelle definiert.

Im IExchangeRateService konzentrieren wir uns auf den IExchangeRateService . Die Geschäftslogik für diesen Dienst wird von einem externen Webdienst implementiert. Ihr Konzept ist jedoch immer noch Teil der Domäne und wird von dieser Schnittstelle repräsentiert.

Infrastruktur

Die Implementierung der externen Abhängigkeiten ist Teil der Infrastruktur der Anwendung:

My Solution
+ My.Product.Core (My.Product.dll)
- My.Product.Infrastructure (My.Product.Infrastructure.dll)
  - DomainServices
      XEExchangeRateService
    SqlServerProductRepository

XEExchangeRateService implementiert den IExchangeRateService Domänendienst durch Kommunikation mit xe.com . Diese Implementierung kann von Ihren Anwendungen, die Ihr Domänenmodell verwenden, verwendet werden, indem die Infrastrukturbaugruppe einbezogen wird.

Anwendung

Beachten Sie, dass ich Anwendungsdienste noch nicht erwähnt habe. Wir werden uns diese jetzt anschauen. Nehmen wir an, wir möchten eine IExchangeRateService Implementierung IExchangeRateService , die einen Cache für schnelle IExchangeRateService verwendet. Der Umriss dieser Dekorationsklasse könnte so aussehen.

public class CachingExchangeRateService : IExchangeRateService
{
    private IExchangeRateService service;
    private ICache cache;

    public CachingExchangeRateService(IExchangeRateService service, ICache cache)
    {
        this.service = service;
        this.cache = cache;
    }

    // Implementation that utilizes the provided service and cache.
}

Beachten Sie den ICache Parameter? Dieses Konzept gehört nicht zu unserer Domain, es handelt sich also nicht um einen Domain-Service. Es ist ein Anwendungsdienst . Es ist eine Abhängigkeit unserer Infrastruktur, die von der Anwendung bereitgestellt werden kann. Lassen Sie uns eine Anwendung vorstellen, die dies demonstriert:

My Solution
- My.Product.Core (My.Product.dll)
  - DomainServices
      IExchangeRateService
    Product
    ProductFactory
    IProductRepository
- My.Product.Infrastructure (My.Product.Infrastructure.dll)
  - ApplicationServices
      ICache
  - DomainServices
      CachingExchangeRateService
      XEExchangeRateService
    SqlServerProductRepository
- My.Product.WcfService (My.Product.WcfService.dll)
  - ApplicationServices
      MemcachedCache
    IMyWcfService.cs
  + MyWcfService.svc
  + Web.config

Das alles kommt in der Anwendung so zusammen:

// Set up all the dependencies and register them in the IoC container.
var service = new XEExchangeRateService();
var cache = new MemcachedCache();
var cachingService = new CachingExchangeRateService(service, cache);

ServiceLocator.For<IExchangeRateService>().Use(cachingService);

Zusammenfassung

Eine vollständige Anwendung besteht aus drei Hauptschichten:

  • Domain
  • Infrastruktur
  • Anwendung

Der Domänenlayer enthält die Domänenentitäten und eigenständigen Domänendienste. Alle Domänenkonzepte (einschließlich Domänen-Services, aber auch Repositorys), die von externen Ressourcen abhängen, werden über Schnittstellen definiert.

Die Infrastrukturschicht enthält die Implementierung der Schnittstellen von der Domänenschicht. Diese Implementierungen können neue Nicht-Domänenabhängigkeiten einführen, die der Anwendung bereitgestellt werden müssen. Dies sind die Anwendungsdienste und werden durch Schnittstellen repräsentiert.

Die Anwendungsschicht enthält die Implementierung der Anwendungsdienste. Die Anwendungsschicht kann auch zusätzliche Implementierungen von Domänenschnittstellen enthalten, wenn die von der Infrastrukturschicht bereitgestellten Implementierungen nicht ausreichen.

Obwohl diese Perspektive möglicherweise nicht mit der allgemeinen DDD-Definition von Diensten übereinstimmt, trennt sie die Domäne von der Anwendung und ermöglicht es Ihnen, die Domänen- (und Infrastruktur-) Assembly zwischen mehreren Anwendungen zu teilen.


Sammlungen: Es spielt keine Rolle.

Es besteht ein deutlicher Unterschied zwischen Sammlungen und Referenzen als Navigationseigenschaften. Eine Referenz ist eine Entität. Eine Sammlung enthält Entitäten. Dies bedeutet, dass die Initialisierung einer Sammlung in Bezug auf die Geschäftslogik bedeutungslos ist: Sie definiert keine Zuordnung zwischen Entitäten. Das Einstellen einer Referenz funktioniert.

Es ist also nur eine Frage der Präferenz, ob oder wie Sie eingebettete Listen initialisieren.

Was das "Wie" betrifft, bevorzugen einige Leute eine faule Initialisierung:

private ICollection<Address> _addresses;

public virtual ICollection<Address> Addresses
{ 
    get { return this._addresses ?? (this._addresses = new HashSet<Address>());
}

Es verhindert Null-Referenz-Exceptions, erleichtert also Unit-Testing und Manipulation der Collection, verhindert aber auch unnötige Initialisierung. Letzteres kann einen Unterschied machen, wenn eine Klasse relativ viele Sammlungen hat. Der Nachteil ist, dass es relativ viel Klempnerei, besonders benötigt. im Vergleich zu Auto-Eigenschaften ohne Initialisierung. Außerdem hat das Aufkommen des Nullfortpflanzungsoperators in C # das Initialisieren von Auflistungseigenschaften weniger dringend gemacht.

... sofern nicht explizit geladen wird

Die Initialisierung von Auflistungen macht es nur schwer zu überprüfen, ob eine Auflistung von Entity Framework geladen wurde. Wenn eine Sammlung initialisiert wird, wird eine Anweisung wie ...

var users = context.Users.ToList();

... erstellt User Objekte mit leeren, nicht-null- Addresses Sammlungen (Lazy Loading zur Seite). Überprüfen, ob die Sammlung geladen ist, erfordert Code wie ...

var user = users.First();
var isLoaded = context.Entry(user).Collection(c => c.Addresses).IsLoaded;

Wenn die Sammlung nicht initialisiert wird, genügt ein einfacher null Check. Wenn selektives explizites Laden ein wichtiger Teil Ihrer Programmierpraxis ist, dh ...

if (/*check collection isn't loaded*/)
    context.Entry(user).Collection(c => c.Addresses).Load();

... ist es möglicherweise bequemer, die Auflistungseigenschaften nicht zu initialisieren.

Referenzeigenschaften: Nicht

Referenzeigenschaften sind Entitäten. Daher ist es sinnvoll , ihnen ein leeres Objekt zuzuweisen .

Schlimmer noch, wenn Sie sie im Konstruktor initiieren, überschreibt EF sie nicht, wenn Sie Ihr Objekt materialisieren oder durch Lazy Loading. Sie werden immer ihre Anfangswerte haben, bis Sie sie aktiv ersetzen. Schlimmer noch, Sie können sogar leere Entitäten in der Datenbank speichern!

Und es gibt noch einen anderen Effekt: Die Reparatur der Beziehung wird nicht stattfinden. Relationship Fixup ist der Prozess, bei dem EF alle Entitäten im Kontext anhand ihrer Navigationseigenschaften verbindet. Wenn ein User und eine Licence separat geladen werden, wird immer noch User.License und umgekehrt. Außer natürlich, wenn License im Konstruktor initialisiert wurde. Dies gilt auch für 1: n-Assoziationen. Wenn Address einen User in seinem Konstruktor initialisiert, werden User.Addresses nicht User.Addresses !

Entität Framework Kern

Relationship-Fixup in Entity Framework-Kern (2.1 zum Zeitpunkt des Schreibens) wird nicht durch initialisierte Referenznavigationseigenschaften in Konstruktoren beeinflusst. Das heißt, wenn Benutzer und Adressen getrennt aus der Datenbank abgerufen werden, werden die Navigationseigenschaften ausgefüllt.
Lazy Loading überschreibt jedoch nicht initialisierte Referenznavigationseigenschaften. Zusammenfassend lässt sich sagen, dass auch bei der EF-Core-Initialisierung die Referenznavigationseigenschaften in Konstruktoren Probleme verursachen können. Tu es nicht. Es ergibt keinen Sinn,







architecture domain-driven-design