DDD-Repositorys im Anwendungs- oder Domänendienst

29

Ich lerne derzeit DDD und habe einige Fragen zum Verwalten von Repositorys mit DDD.

Eigentlich habe ich zwei Möglichkeiten getroffen:

Erster

Die erste Möglichkeit zum Verwalten von Diensten, die ich gelesen habe, besteht darin, einem Anwendungsdienst ein Repository und ein Domänenmodell hinzuzufügen.

Auf diese Weise rufen wir in einer der Anwendungsdienstmethoden eine Domänendienstmethode (Überprüfung von Geschäftsregeln) auf. Wenn die Bedingung gut ist, wird das Repository mit einer speziellen Methode aufgerufen, um die Entität aus der Datenbank zu erhalten / abzurufen.

Ein einfacher Weg, dies zu tun, könnte sein:

class ApplicationService{

  constructor(domainService, repository){
    this.domainService = domainService
    this.repository = repository
  }

  postAction(data){
    if(this.domainService.validateRules(data)){
      this.repository.persist(new Entity(data.name, data.surname))
    }
    // ...
  }

}

Der zweite

Die zweite Möglichkeit besteht darin, das Repository stattdessen in den domainService einzuschleusen und das Repository nur über den domainService zu verwenden:

class ApplicationService{

  constructor(domainService){
    this.domainService = domainService
  }

  postAction(data){
    if(this.domainService.persist(data)){
      console.log('all is good')
    }
    // ...
  }

}

class DomainService{

  constructor(repository){
    this.repository = repository
  }

  persist(data){
    if(this.validateRules(data)){
      this.repository.save(new Entity(data.name))
    }
  }

  validateRules(data){
    // returns a rule matching
  }

}

Von nun an kann ich nicht mehr unterscheiden, welches das beste ist (wenn es eines am besten gibt) oder was beide in ihrem Kontext implizieren.

Können Sie mir ein Beispiel geben, wo eines besser sein könnte als das andere und warum?

mfrachet
quelle
msgstr "ein Repository und ein Domänenmodell in einen Anwendungsdienst einschleusen." Was meinen Sie damit, irgendwo ein "Domain-Modell" einzuspritzen? AFAICT im Sinne des DDD-Domänenmodells bedeutet die gesamte Reihe von Konzepten aus der Domäne und die Wechselwirkungen zwischen ihnen, die für die Anwendung relevant sind. Es ist eine abstrakte Sache, es ist kein In-Memory-Objekt. Sie können es nicht injizieren.
Alexey

Antworten:

31

Die kurze Antwort lautet: Sie können Repositorys von einem Anwendungsdienst oder einem Domänendienst verwenden. Es ist jedoch wichtig zu überlegen, warum und wie Sie dies tun.

Zweck eines Domain Service

Domänendienste sollten Domänenkonzepte / -logik kapseln - als solche die Domänendienstmethode:

domainService.persist(data)

gehört nicht zu einem Domänendienst, da er persistnicht Teil der allgegenwärtigen Sprache ist und die Funktionsweise der Persistenz nicht Teil der Geschäftslogik der Domäne ist.

Im Allgemeinen sind Domänendienste nützlich, wenn Sie über Geschäftsregeln / -logiken verfügen, die eine Koordinierung oder die Arbeit mit mehr als einem Aggregat erfordern. Wenn die Logik nur ein Aggregat umfasst, sollte sie sich in einer Methode für die Entitäten dieses Aggregats befinden.

Repositorys in Application Services

In diesem Sinne bevorzuge ich in Ihrem Beispiel Ihre erste Option - aber auch da gibt es Raum für Verbesserungen, da Ihr Domain - Service Rohdaten von der API akzeptiert - warum sollte der Domain - Service über die Struktur von Bescheid wissen data? Darüber hinaus scheinen die Daten nur auf ein einzelnes Aggregat bezogen zu sein, sodass die Verwendung eines Domänendienstes dafür nur einen begrenzten Wert bietet. Im Allgemeinen würde ich die Validierung im Entitätskonstruktor ablegen. z.B

postAction(data){

  Entity entity = new Entity(data.name, data.surname);

  this.repository.persist(entity);

  // ...
}

und eine Ausnahme auslösen, wenn es ungültig ist. Abhängig von Ihrem Anwendungsframework kann es einfach sein, einen konsistenten Mechanismus zum Abfangen der Ausnahme und zum Zuordnen zu der entsprechenden Antwort für den API-Typ zu haben - z. B. für eine REST-API einen 400-Statuscode zurückgeben.

Repositorys in Domänendiensten

Ungeachtet des oben Gesagten ist es manchmal nützlich, ein Repository in einen Domänendienst einzuschleusen und diesen zu verwenden, aber nur, wenn Ihre Repositorys so implementiert sind, dass sie nur Aggregatwurzeln akzeptieren und zurückgeben, und wenn Sie Logik abstrahieren, die mehrere Aggregate umfasst. z.B

postAction(data){

  this.domainService.doSomeBusinessProcess(data.name, data.surname, data.otherAggregateId);

  // ...
}

Die Implementierung des Domänendienstes würde folgendermaßen aussehen:

doSomeBusinessProcess(name, surname, otherAggregateId) {

  OtherEntity otherEntity = this.otherEntityRepository.get(otherAggregateId);

  Entity entity = this.entityFactory.create(name, surname);

  int calculationResult = this.someCalculationMethod(entity, otherEntity);

  entity.applyCalculationResultWithBusinessMeaningfulName(calculationResult);

  this.entityRepository.add(entity);

}

Fazit

Der Schlüssel hierbei ist, dass der Domänendienst einen Prozess kapselt , der Teil der allgegenwärtigen Sprache ist. Um seine Rolle zu erfüllen, muss es Repositorys verwenden - und das ist völlig in Ordnung.

Das Hinzufügen eines Domänendienstes, der ein Repository mit einer Methode namens persistumschließt , bietet jedoch nur einen geringen Mehrwert.

Auf dieser Basis ist es kein Problem, das Repository direkt vom Anwendungsservice aus zu verwenden, wenn Ihr Anwendungsservice einen Anwendungsfall ausdrückt, bei dem nur mit einem einzelnen Aggregat gearbeitet werden muss.

Chris Simon
quelle
Okay, wenn ich Geschäftsregeln habe (die Spezifikationsmusterregeln zulassen), wenn es sich nur um eine Entität handelt, sollte ich aber die Validierung in dieser Entität durchführen? Es scheint seltsam zu sein, Geschäftsregeln wie das Steuern eines guten Benutzer-Mail-Formats in die Benutzerentität einzufügen. Ist es nicht In Bezug auf die globale Antwort, danke. Es wurde festgestellt, dass es keine "Standardregel" gibt, die angewendet werden muss, und dies hängt wirklich von unseren Verwendungszwecken ab. Ich habe einige Arbeiten zu erledigen, um all diesen Job gut zu unterscheiden
mfrachet
2
Zur Verdeutlichung sind die Regeln, die zur Entität gehören, nur die Regeln, die in der Verantwortung dieser Entität liegen. Ich bin damit einverstanden, dass die Steuerung eines guten Benutzer-E-Mail-Formats nicht so aussieht, als ob es zur Entität Benutzer gehört. Persönlich möchte ich Validierungsregeln wie diese in ein Wertobjekt einfügen, das eine E-Mail-Adresse darstellt. Der Benutzer hätte eine Eigenschaft vom Typ EmailAddress, und der EmailAddress-Konstruktor akzeptiert eine Zeichenfolge und löst eine Ausnahme aus, wenn die Zeichenfolge nicht dem erforderlichen Format entspricht. Anschließend können Sie das EmailAddress ValueObject für andere Entitäten wiederverwenden, die eine E-Mail-Adresse speichern müssen.
Chris Simon
Okay, ich verstehe, warum ich Value Object jetzt verwende. Es bedeutet jedoch, dass das Wertobjekt eine Eigenschaft besitzt, die die Geschäftsregel ist, die das Format verwaltet.
mfrachet
1
Wertobjekte sollten unveränderlich sein. Im Allgemeinen bedeutet dies, dass Sie im Konstruktor initialisieren und validieren und für alle Eigenschaften das öffentliche get / private Set-Muster verwenden. Sie können jedoch Sprachkonstrukte verwenden, um Gleichheit, ToString-Prozess usw. zu definieren. Beispiel: kacper.gunia.me/ddd-building-blocks-in-php-value-object oder github.com/spring-projects/spring-gemfire-examples/ blob / master /…
Chris Simon
Vielen Dank, @ChrisSimon, endlich und Antwort auf eine reale DDD-Situation, die Code und nicht nur Theorie beinhaltet. Ich habe 5 Tage lang SO und das Web nach einem Funktionsbeispiel für das Erstellen und Speichern eines Aggregats durchsucht. Dies ist die klarste Erklärung, die ich gefunden habe.
e_i_pi
2

Es gibt ein Problem mit der akzeptierten Antwort:

Das Domain-Modell darf nicht vom Repository abhängen und der Domain-Service ist Teil des Domain-Modells -> Der Domain-Service sollte nicht vom Repository abhängen.

Sie sollten stattdessen alle Entitäten zusammenstellen, die für die Ausführung der Geschäftslogik bereits im Anwendungsservice erforderlich sind, und dann Ihre Modelle nur mit instanziierten Objekten versehen.

Nach Ihrem Beispiel könnte es so aussehen:

class ApplicationService{

  constructor(domainService, repository){
    this.domainService = domainService
    this.repositoryA = repositoryA
    this.repositoryB = repositoryB
    this.repositoryC = repositoryC
  }

  // any parsing and/or pre-business validation already happened in controller or whoever is a caller
  executeUserStory(data){
    const entityA = this.repositoryA.get(data.criterionForEntityA)
    const entityB = this.repositoryB.get(data.criterionForEntityB)

    if(this.domainService.validateSomeBusinessRules(entityA, entityB)){
      this.repositoryC.persist(new EntityC(entityA.name, entityB.surname))
    }
    // ...
  }
}

Faustregel: Das Domain-Modell hängt nicht von den äußeren Schichten ab

Application vs Domain Service Aus diesem Artikel :

  • Domänendienste sind sehr detailliert, da Anwendungsdienste eine Fassade für die Bereitstellung einer API darstellen.

  • Domänendienste enthalten Domänenlogik, die natürlich nicht in eine Entität oder ein Wertobjekt eingefügt werden kann, wohingegen Anwendungsdienste die Ausführung der Domänenlogik koordinieren und selbst keine Domänenlogik implementieren.

  • Domänendienstmethoden können andere Domänenelemente als Operanden und Rückgabewerte haben, wohingegen Anwendungsdienste triviale Operanden wie Identitätswerte und primitive Datenstrukturen verarbeiten.

  • Anwendungsdienste deklarieren Abhängigkeiten von Infrastrukturdiensten, die zum Ausführen der Domänenlogik erforderlich sind.

SMS
quelle
1

Keines Ihrer Muster ist gut, es sei denn, Ihre Dienste und Objekte kapseln eine zusammenhängende Reihe von Verantwortlichkeiten.

Sagen Sie zunächst, was Ihr Domain-Objekt ist, und besprechen Sie, was es in der Domain-Sprache tun kann. Wenn es gültig oder ungültig sein kann, warum sollte es nicht eine Eigenschaft des Domänenobjekts selbst sein?

Wenn zum Beispiel die Gültigkeit eines Objekts nur in Bezug auf ein anderes Objekt Sinn macht, haben Sie möglicherweise eine Verantwortlichkeits-Validierungsregel X für Domänenobjekte, die in eine Reihe von Diensten eingekapselt werden kann.

Muss ein Objekt zur Validierung in Ihren Geschäftsregeln gespeichert werden? Wahrscheinlich nicht. Die Verantwortung für das Speichern von Objekten liegt normalerweise in einem separaten Repository-Objekt.

Nun möchten Sie eine Operation ausführen, die eine Reihe von Verantwortlichkeiten abdeckt, ein Objekt erstellt, validiert und, falls gültig, speichert.

Gehört dieser Vorgang zum Domänenobjekt? Machen Sie es dann zu einem Teil dieses Objekts, zExamQuestion.Answer(string answer)

Passt es zu einem anderen Teil Ihrer Domain? Leg es dort hinBasket.Purchase(Order order)

Möchten Sie lieber ADM REST-Services ausführen? OK dann.

Controller.Post(json) 
{ 
    parse(json); 
    verify(parsedStruct); 
    save(parsedStruct); 
    return 400;
}
Ewan
quelle