2013-06-08 9 views
5

Die allgemein bekannte Empfehlung in DDD ist, dass eine Aggregatwurzeln einen Domäne-Dienst nicht verwenden. Der Domänenservice koordiniert zwei Aggregatwurzeln, um ein Verhalten zu erzielen.In Domäne-Dienst in den AggregateRoots in DDD einfügen

Es hat mich wirklich überrascht, als ich diesen Blog von Rinat Abdullin mit dem Titel Building Blocks Of CQRS sah. Unter dem Abschnitt Domain Service werden Sie lesen, dass ein Domäne-Dienst in einem Aggregatstamm injiziert wird.

Kann ein Aggregat-Root einen Domain-Service akzeptieren?

Antwort

4

Bitte, Mißachtung, dass Artikel. Es wurde vor langer Zeit geschrieben und ist völlig falsch. Wenn ein Modul mit AggregateRoot Umsetzung und Domainservice Mustern, würde ich empfehlen, eine höhere Logik (zB Request-Handler) zu haben, die für verantwortlich:

  1. Laden des Aggregat
  2. Berechnungen durchführen mit Hilfe von Domain-Dienste
  3. Mutieren des Aggregatzustandes entsprechend.
+1

Rinat, willst du nicht ein Banner auf diesen alten Artikeln von dir platzieren, das wird das wiederholen, was du in der Antwort geschrieben hast? Leute verweisen oft auf Ihren Blog für Ideen und Best Practices, aber Sie wissen sicherlich, dass einige der beschriebenen Praktiken nicht wirklich gut sind ... Nichts für ungut, nur ein Vorschlag. –

3

Es ist sehr schwierig, injizieren alles in Domain-Objekte, und damit ist ziemlich Technologie spezifisch. In Java erfordert es Kompilierzeit Weben von Aspekten in Ihre Domain-Klassen. Und obwohl ich mich darin irren könnte, denke ich, dass die meisten DDD-Führer denken, dass dies im Allgemeinen eine schlechte Idee ist. Sowohl Evans als auch Vernon entmutigen beide aktiv und ich höre ihnen gerne zu. Für eine vollständige Erklärung, lesen Sie Vernon.

+0

Ich stimme zu, dass Evans und Vernon der Idee von Rinat widersprechen. Im Beispiel verwendet er einen Preisservice, um einen Schwellenwert in der Methode LockCustomerForAccountOverdraft zu finden. Dies ist eine Geschäftsregel-Bewertung, die durch Senden eines Befehls durchgeführt werden kann. Mit dem Ansatz von Evan und Verson sollte es einen höheren Domain-Service namens LockCustomer geben, wo die Koordination von PricingService und CustomerAggregate erfolgen kann. Ich denke Evans und Vernon wenden SRP an, also gibt es mehr Komponenten. Rinat verfolgt einen einfachen Ansatz, so dass er in diesem Fall für den Kunden das Prinzip offen/geschlossen brechen kann. –

7

In gewisser Weise ja. Wenn der AR wirklich einen Dienst benötigt, um einige seiner Arbeit zu tun, dann können Sie es als Methode Argument injizieren. Wenn das AR einen Dienst für am meisten seines Verhaltens benötigt, dann ist es wahrscheinlich falsch modelliert.

+0

Wenn beide Dienste im selben beschränkten Kontext leben, können die aggregierten Roots über Nachrichten miteinander kommunizieren. Warum ist also ein Service injizierend? –

+1

Es wird NUR benötigt, wenn das AR es benötigt, um ein bestimmtes Verhalten auszuführen. Ich sehe nicht, wie Messaging hier helfen würde. – MikeSW

+0

Ich könnte hier den falschen Begriff verwendet haben. Ich meinte Domain-Events. –

2

Ich finde die following explanation ziemlich gut. Es basiert auf dem Buch von Vaughn Vernon und "injiziert" den Domain-Service im Domain-Modell über den Methodenaufruf, der diesen Service tatsächlich benötigt.

public class PurchaseOrder 
{ 
    public string Id { get; private set; } 
    public string VendorId { get; private set; } 
    public string PONumber { get; private set; } 
    public string Description { get; private set; } 
    public decimal Total { get; private set; } 
    public DateTime SubmissionDate { get; private set; } 
    public ICollection<Invoice> Invoices { get; private set; } 

    public decimal InvoiceTotal 
    { 
     get { return this.Invoices.Select(x => x.Amount).Sum(); } 
    } 

    public bool IsFullyInvoiced 
    { 
     get { return this.Total <= this.InvoiceTotal; } 
    } 

    bool ContainsInvoice(string vendorInvoiceNumber) 
    { 
     return this.Invoices.Any(x => x.VendorInvoiceNumber.Equals(
      vendorInvoiceNumber, StringComparison.OrdinalIgnoreCase)); 
    } 

    public Invoice Invoice(IInvoiceNumberGenerator generator, 
     string vendorInvoiceNumber, DateTime date, decimal amount) 
    { 
     // These guards maintain business integrity of the PO. 
     if (this.IsFullyInvoiced) 
      throw new Exception("The PO is fully invoiced."); 
     if (ContainsInvoice(vendorInvoiceNumber)) 
      throw new Exception("Duplicate invoice!"); 

     var invoiceNumber = generator.GenerateInvoiceNumber(
      this.VendorId, vendorInvoiceNumber, date); 

     var invoice = new Invoice(invoiceNumber, vendorInvoiceNumber, date, amount); 
     this.Invoices.Add(invoice); 
     DomainEvents.Raise(new PurchaseOrderInvoicedEvent(this.Id, invoice.InvoiceNumber)); 
     return invoice; 
    } 
} 

public class PurchaseOrderService 
{ 
    public PurchaseOrderService(IPurchaseOrderRepository repository, 
     IInvoiceNumberGenerator invoiceNumberGenerator) 
    { 
     this.repository = repository; 
     this.invoiceNumberGenerator = invoiceNumberGenerator; 
    } 

    readonly IPurchaseOrderRepository repository; 
    readonly IInvoiceNumberGenerator invoiceNumberGenerator; 

    public void Invoice(string purchaseOrderId, 
     string vendorInvoiceNumber, DateTime date, decimal amount) 
    { 
     // Transaction management, along with committing the unit of work 
     // can be moved to ambient infrastructure. 
     using (var ts = new TransactionScope()) 
     { 
      var purchaseOrder = this.repository.Get(purchaseOrderId); 
      if (purchaseOrder == null) 
       throw new Exception("PO not found!"); 
      purchaseOrder.Invoice(this.invoiceNumberGenerator, 
       vendorInvoiceNumber, date, amount); 
      this.repository.Commit(); 
      ts.Complete(); 
     } 
    } 
} 
+0

Der PurchaseOrderService ist ein Anwendungsdienst, kein Domänen-Dienst, wie ich es verstehe. –

+2

@wonderfulworld: IInvoiceNumberGenerator ist der Domain-Service – mynkow