Blog

Domänenorientiertes Design Teil 2 - Anwendungsdienste und Domänendienste

Aktualisiert Oktober 22, 2025
6 Minuten

Zuvor habe ich Ihnen eine Reihe von Bibliotheken vorgestellt, mit denen Sie eine Anwendung mit Domain-Driven Design und CQRS/Event Sourcing erstellen können. Im ersten Beitrag habe ich Ihnen die Baugruppen PGS.DDD.Domain und PGS.DDD.Data gezeigt, die wir zur Implementierung der ersten Aggregate Root in unserer internen Link Sharing Plattform verwendet haben. In diesem Teil möchte ich Ihnen kurz zeigen, wie wir in unserer Implementierung an Dienste herangehen.

Was sind Dienstleistungen?

Der Begriff Service wird oft verwendet und ist etwas überladen. Daher halte ich ihn für leicht veraltet und seine genaue Bedeutung für schwer zu fassen. Einige Entwickler neigen dazu, ein breites Spektrum von Verantwortlichkeiten auf die "Diensteschicht" zu legen. Abgesehen von einem anämischen Domänenmodell (das ich übrigens für ein Anti-Pattern halte), sind Dienste im Allgemeinen ein wichtiger Bestandteil einer Anwendung - ob DDD oder nicht.

Eric Evans führt Dienste als die Realisierung wichtiger Prozesse ein, die nicht zu einer bestimmten Entität oder einem Aggregat Root gehören. Diese Klassen, die Domain Services genannt werden, müssen der Ubiquitous Language folgen und sind Teil der Domain-Schicht.

Es gibt auch Anwendungsdienste, die außerhalb der Domäne leben und als Fassade für komplexe Geschäftsprozesse verwendet werden können und auf Infrastruktur und externe Abhängigkeiten zugreifen können. Anwendungsdienste dienen in der Regel auch als oberste Schicht, direkt unter der API-Schnittstellenschicht, z.B. einem RESTful Web Service oder einer Bibliothek.

Unterscheidung der Servicetypen

Für DDD-Neulinge kann es eine Herausforderung sein, zu entscheiden, ob eine bestimmte Funktionalität ein Domänen- oder Anwendungsdienst werden soll. Die Antwort ist nicht immer einfach und Sie sollten sich von den Experten der Domäne beraten lassen.

Domain-Dienste

Der Domänenservice sollte direkt mit der Kernfunktionalität des Unternehmens verbunden sein. Ein gängiges Beispiel ist die Zuweisung von Rechnungsnummern. Es könnte eine Aufgabe der Rechnung selbst sein, diese Nummern in Serie oder nach bestimmten Regeln zu generieren und dabei die Kennung des Kunden, das Datum usw. zu berücksichtigen. Das mag jedoch einigen Leuten seltsam vorkommen. Und eine solche Entscheidung könnte in komplexeren Fällen zu Problemen führen, z.B. wenn die Rechnungsnummern von Kunde zu Kunde unterschiedlich generiert werden oder Zugriff auf einen externen Status erfordern. Wenn dies der Fall ist, könnte die Verantwortung bei einem Domänenservice liegen. Solche Domänendienste können von einem Aggregatstamm konsumiert oder von einem Anwendungsdienst aufgerufen werden, der das Ergebnis nur an die Domäne weitergibt.

Technischere Arten von Domain Services sind Fabriken und Repositories. Sie sind Teil der Domäne (zumindest die Schnittstellen). Im Falle von Repositories ist es weniger offensichtlich, dass Fabriken sehr wohl dazugehören. Fabriken, die zur Erstellung von Aggregatwurzeln und/oder Entitäten verwendet werden, erfordern Wissen, das vom Domänenexperten stammt, und arbeiten auf der Grundlage von Geschäftsentscheidungen. Betrachten Sie ein hypothetisches VehicleFactory, das Instanzen von Car oder Truck Entitäten in Abhängigkeit von den Eigenschaften des Fahrzeugs (Größe, Masse usw.) erstellt.

Schließlich wird ein Domänenservice häufig verwendet, wenn eine bestimmte Operation auf mehr als eine Aggregatwurzel zugreifen muss. Viele würden sagen, dass jeder Vorgang nur eine einzige Aggregatwurzel aktualisieren sollte, aber das ist nicht immer möglich. Vor allem dann nicht, wenn sich die Anforderungen an eine bestehende Anwendung ändern. Wenn dies erforderlich ist, sollte ein Domänenservice verwendet werden, anstatt dass Aggregate Roots aufeinander zugreifen.

Anwendungsdienste

Ein typisches Beispiel für einen Anwendungsdienst ist hingegen der Einstiegspunkt in die Funktionalität der Anwendung. Dies ist das häufigste Beispiel für einen Dienst, auf den von der API aus zugegriffen wird (z. B. MVC-Controller). Ein Anwendungsdienst führt Aufgaben aus, die nicht direkt mit der Geschäftslogik zusammenhängen - er orchestriert die Erstellung anderer Dienste, kümmert sich um Transaktionen und Persistenz usw. Ich persönlich bin der Meinung, dass ein Anwendungsdienst (oder zumindest eine Methode des Anwendungsdienstes) direkt einem einzelnen geschäftlichen Anwendungsfall entsprechen sollte.

Schließlich gibt es Anwendungsdienste, die nicht den Kern der Domäne bilden, sondern Teil eines größeren Prozesses sind. Ein gängiger Fall könnte ein (asynchroner) E-Mail-Benachrichtigungsdienst sein. Ob das Senden einer Benachrichtigung erfolgreich ist oder nicht, ist für den Abschluss der eigentlichen Geschäftsaktion möglicherweise nicht entscheidend. Daher würde er nicht zu einem Domänendienst werden. Er würde auch nicht Teil eines Anwendungsdienstes werden, um das Prinzip der einzigen Verantwortung nicht zu verletzen. In der Praxis könnte er sogar asynchron und völlig unabhängig vom Anwendungsdienst arbeiten. Dies lässt sich durch die Verwendung von Domänenereignissen und einer Methode zu deren Verteilung realisieren (Service Bus oder Pub/Sub sind zwei Möglichkeiten).

Beispiel für Dienste auf der Link Sharing Plattform

Der erste Anwendungsdienst, den wir erstellt haben, verarbeitet einen übermittelten Link, erstellt eine neue Aggregate Root-Instanz, persistiert sie und schickt die Änderungen über einen Service Bus an interessierte Parteien. Hier ist die betreffende Methode:

public class SubmittingLinkService : ISubmittingLinkService
{
  public CommandResult SubmitLink(SubmitLinkOnBehalfCommand Befehl)
  {
  var linkValidator = new LinkValidator();

  if (linkValidator.IsValid(command.Uri) == false)
  {
  return CommandResult.Failure();
  }

  var link = new Link(command.Uri, new SubmitterId(command.SubmitterId), new Description(command.Description), _clock.Now);

  _repository.Save(link);
  var changes = ((IAggregateRoot)link).Changes.ToArray();

  _serviceBus.Publish(changes);

  return CommandResult.Success(changes);
  }
}

Sicherlich passiert hier eine Menge und Teile dieser Methode könnten in eine gemeinsame Basis überführt werden, aber dafür ist es noch nicht an der Zeit. Alles in allem besteht diese Methode aus drei Teilen. Vorbereiten und Validieren der Eingabe, Ausführen einer Geschäftsaktion (in diesem Fall das Erstellen einer Aggregatwurzel) und abschließend Ausführen einiger Aktionen in der Infrastruktur. So einfach ist das.

Übrigens - sehen Sie die Klasse LinkValidator? Dies ist ein weiteres Beispiel für Domain Services - Domainvalidierung. Wir haben uns hier entschieden, dass die Überprüfung, ob wir eine bestimmte eingereichte URL akzeptieren wollen, nicht in der Verantwortung der Linkklasse liegt. Erstens könnte das ziemlich kompliziert werden und zweitens haben wir beschlossen, dass der Domänenexperte (für die Zwecke unseres Projekts bin ich das) sich nicht um die tatsächliche Gültigkeit kümmern sollte. In diesem Stadium wird davon ausgegangen, dass die URL richtig geformt ist, und der Validator prüft nur, ob sie das HTTP-Schema verwendet usw. Eine allgemeinere Validierung, wie die Überprüfung von Pflichtfeldern und Feldtypen, gehört in die API-Schicht.

Alternativ könnten wir auch eine Validator-Instanz über den Konstruktor an Link übergeben, haben uns aber dagegen entschieden, um Ausnahmen für den Kontrollfluss zu vermeiden. Außerdem ist es auf diese Weise einfacher, die Typen isoliert zu testen.

Was ist mit PGS.DDD.Application?

Die Klasse SubmittingLinkService erbt derzeit nicht von einem gemeinsamen Basistyp. Wir müssen noch herausfinden, was wir brauchen. Derzeit enthält das Paket PGS.DDD.Application nur eine einzige Klasse CommandResult, die wir von den Methoden des Anwendungsdienstes zurückgeben und die wir verwenden, um über das allgemeine Ergebnis in der API-Schicht zu entscheiden. In unserem Fall wäre das eine HTTP-Antwort.

Wie ich oben geschrieben habe, werden wir wahrscheinlich eine oder mehrere ApplicationService-Basisklassen einführen, die häufige Fälle der Handhabung des Service Bus, des Repository usw. kapseln würden. Wir wollten nur nicht zu weit vorgreifen.

Nächste

Im nächsten und vorerst letzten Beitrag werde ich Ihnen unsere Lesemodelle vorstellen.

Weitere Lektüre

Contact

Let’s discuss how we can support your journey.