2009-08-07 11 views
2

Ich habe eine Domäne Aggregat, nennen Sie es "Bestellung", die eine Liste von OrderLines enthält. Der Auftrag verfolgt die Summe des Betrags in den Auftragspositionen. Der Kunde hat einen laufenden "Guthabensaldo", den er bestellen kann, indem er die Geschichte seiner Datenbanktransaktionen summiert. Sobald sie das ganze Geld im "Pool" aufgebraucht haben, können sie keine Produkte mehr bestellen.Abrufen zusätzlicher Daten für eine Domänenentität

Also jedes Mal, wenn eine Zeile zur Bestellung hinzugefügt wird, muss ich überprüfen, wie viel im Pool übrig ist und ob die Bestellung sie übersteuert. Der Betrag im Pool ändert sich ständig, da andere verwandte Kunden ihn kontinuierlich nutzen.

Die Frage ist, in Bezug auf DDD, wie bekomme ich diese Menge, da ich meine Domain Layer mit DataContext Bedenken nicht belasten will (mit L2S hier). Da ich nicht einfach von der Domäne aus in die Datenbank abfragen kann, wie würde ich diese Daten erhalten, damit ich die Geschäftsregel validieren kann?

Ist dies eine Instanz, in der Domänenereignisse verwendet werden?

Antwort

5

Ihr Auftragsaggregat sollte vollständig gekapselt sein. Es muss daher in der Lage sein zu bestimmen, ob es zulässig ist, einen Artikel hinzuzufügen, d. H., Ob der Kundenkredit überschritten wird oder nicht. Es gibt verschiedene Möglichkeiten, dies zu tun, aber alle hängen davon ab, dass das Auftragsrepository ein spezifisches Aggregat zurückgibt, das weiß, wie man diese bestimmte Sache macht. Dies ist wahrscheinlich ein anderes Auftragsaggregat als beispielsweise für die Erfüllung von Bestellungen.

Sie erkennen müssen, dann in Code erfassen, die Tatsache, dass Sie die Bestellung erwar eine besondere Rolle in diesem Fall, das heißt die Rolle der das Hinzufügen von zusätzlichen Positionen zu erfüllen.Dazu erstellen Sie eine Schnittstelle für diese Rolle und ein entsprechendes Aggregat mit der internen Unterstützung für die Rolle.

Dann kann Ihre Serviceebene Ihr Auftragsrepository nach einer Bestellung fragen, die diese explizite Rollenschnittstelle erfüllt, und das Repository hat somit genügend Informationen darüber, was Sie in der Lage sein müssen, etwas zu bauen, das diese Anforderung erfüllen kann.

Zum Beispiel:

public interface IOrder 
{ 
    IList<LineItem> LineItems { get; } 
    // ... other core order "stuff" 
} 

public interface IAddItemsToOrder: IOrder 
{ 
    void AddItem(LineItem item); 
} 

public interface IOrderRepository 
{ 
    T Get<T>(int orderId) where T: IOrder; 
} 

Nun, Ihr Service-Code etwas aussehen würde:

public class CartService 
{ 
    public void AddItemToOrder(int orderId, LineItem item) 
    { 
    var order = orderRepository.Get<IAddItemsToOrder>(orderId); 
    order.AddItem(item); 
    } 
} 

Als nächstes Ihre Order-Klasse, die implementiert IAddItemsToOrder eine Kundeneinheit benötigt, so dass es den Kredit überprüfen Balance. Sie kaskadieren also dieselbe Technik, indem Sie eine bestimmte Schnittstelle definieren. Das Auftragsrepository kann das Kundenrepository aufrufen, um eine Kundenentität zurückzugeben, die diese Rolle erfüllt, und sie dem Auftragsaggregat hinzuzufügen.

So hätten Sie eine Basis ICustomer Schnittstelle und dann eine explizite Rolle in Form einer ICustomerCreditBalance Schnittstelle, die davon abstammt. Die ICustomerCreditBalance fungiert sowohl als Markierschnittstelle zu Ihrem Kundenrepository, um ihr mitzuteilen, wofür Sie den Kunden benötigen, damit sie die entsprechende Kundenentität erstellen kann, und sie verfügt über die Methoden und/oder Eigenschaften, um die spezifische Rolle zu unterstützen. Etwas wie:

public interface ICustomer 
{ 
    string Name { get; } 
    // core customer stuff 
} 

public interface ICustomerCreditBalance: ICustomer 
{ 
    public decimal CreditBalance { get; } 
} 

public interface ICustomerRepository 
{ 
    T Get<T>(int customerId) where T: ICustomer; 
} 

explizite Rolle Schnittstellen geben Repositorys die wichtigsten Informationen sie die richtige Entscheidung treffen müssen, welche Daten aus der Datenbank zu holen, und ob sie eifrig zu holen oder träge.

Beachten Sie, dass ich in diesem Fall die CreditBalance-Eigenschaft auf die ICustomerCreditBalance-Schnittstelle gesetzt habe. Es könnte jedoch auch auf der Basis sein ICustomer Schnittstelle und ICustomerCreditBalance wird dann eine leere "Marker" -Schnittstelle, um das Repository wissen zu lassen, dass Sie das Guthaben abfragen werden. Es geht darum, das Repository wissen zu lassen, welche Rolle Sie für die zurückgegebene Entität haben möchten.

Der letzte Teil, der das alles zusammenbringt, wie Sie in Ihrer Frage erwähnt haben, ist Domain-Events. Der Auftrag kann ein Fehlerdomänenereignis auslösen, wenn das Guthaben des Kunden überschritten wird, um der Serviceebene mitzuteilen, dass der Auftrag ungültig ist. Wenn der Kunde genug Guthaben hat, kann er andererseits entweder das Guthaben auf dem Kundenobjekt aktualisieren oder ein Domain-Ereignis auslösen, um den Rest des Systems darüber zu benachrichtigen, dass das Guthaben reduziert werden muss.

Ich habe den Domain-Event-Code nicht zur CartService-Klasse hinzugefügt, da diese Antwort schon ziemlich lang ist! Wenn du mehr darüber wissen willst, schlage ich vor, dass du eine weitere Frage stellst, die auf dieses spezielle Problem abzielt, und ich werde es dort weiter ausbauen ;-)

+0

Mike, das ist eine ausgezeichnete Antwort in Bezug auf das Aggregat Design. Wenn ich die Gelegenheit im nächsten Sprint bekomme, werde ich versuchen, das, was du hier hast, zu überdenken. Die Art, wie ich dieses Problem gelöst habe, ist durch die Verwendung von Domain-Events, die Udi Dahans neuestem Artikel ähnlich sind [http://www.udidahan.com/2009/06/14/domain-events-salvation/]. Wenn das Ereignis, das die Hinzufügung einer Zeile anzeigt, ausgelöst wird, überprüft der Ereignishandler das Guthaben und legt eine interne Eigenschaft für das Aggregat fest. Jetzt überprüft eine Geschäftsregel diese Eigenschaft und meldet den Erfolg oder Fehler an den Client zurück. – jlembke

+0

Ich denke, dass Domain-Ereignisse für die Benachrichtigung in einer Art "Feuer und vergessen" -Modus verwendet werden sollen. Die Motivation, sie zu haben, besteht darin, Benachrichtigung/Rückruf zuzulassen, ohne dass eine Seite mit der anderen gekoppelt ist. Ihre Lösung mit Domänenereignissen ist durchaus möglich, aber sie wird als eine Art entkoppelter Fernprozeduraufruf verwendet. Ich denke, es ist besser, Domain-Events für One-Way-Benachrichtigungen mit einer Messaging-Metapher zu reservieren. Dies hilft erheblich bei der Skalierbarkeit - Sie können sie dann beispielsweise mit einem Servicebus implementieren. –

+1

Ich habe vergessen zu sagen: Udi's vorherige zwei Artikel über Domain-Events (http://www.udidahan.com/2008/02/29/how-to-create-fully-capsulated-domain-models/) ** auch ** Beantworten Sie Ihre Frage - er verwendet Domain-Ereignisse, um Validierungsfehler zu signalisieren ** und ** die Technik, die ich hier befürworte, um externe Validierungsprüfungen durchzuführen. Also bekommst du 2 für den Preis von 1 in Udi's Domain Events Artikeln ;-) –

2

In einem solchen Szenario entlasten ich die Verantwortung mit Ereignissen oder Delegaten. Vielleicht ist der einfachste Weg, um Sie zu zeigen, mit etwas Code.

Ihre Auftragsklasse wird eine Predicate<T> haben, die verwendet wird, um zu bestimmen, ob die Kreditlinie des Kunden groß genug ist, um die Auftragszeile zu bearbeiten.

public class Order 
{ 
    public Predicate<decimal> CanAddOrderLine; 

    // more Order class stuff here... 

    public void AddOrderLine(OrderLine orderLine) 
    { 
     if (CanAddOrderLine(orderLine.Amount)) 
     { 
      OrderLines.Add(orderLine); 
      Console.WriteLine("Added {0}", orderLine.Amount); 
     } 
     else 
     { 
      Console.WriteLine(
       "Cannot add order. Customer credit line too small."); 
     } 
    } 
} 

Sie haben wahrscheinlich eine CustomerService-Klasse oder etwas ähnliches, um die verfügbare Kreditlinie zu ziehen. Sie legen das CanAddOrderLine-Prädikat fest, bevor Sie Auftragszeilen hinzufügen. Dies führt eine Überprüfung des Guthabens des Kunden jedes Mal durch, wenn eine Zeile hinzugefügt wird.

Kein Zweifel, Ihr wirkliches Szenario wird komplizierter als das sein. Sie können auch den Delegierten Func<T> überprüfen. Ein Delegierter oder ein Ereignis könnte nützlich sein, um den Kreditbetrag zu dekrementieren, nachdem die Auftragszeile platziert wurde, oder um einige Funktionen auszulösen, wenn der Kunde in der Bestellung sein Kreditlimit überschreitet.

Viel Glück!

+0

Ich mag das. Was ich jetzt mache, ist das Einfügen eines Dienstes in das Domänenobjekt, damit es nicht weiß, wo es den Betrag erhält. In einem Konzept, das dem ähnelt, was Sie hier tun. Ich werde das versuchen und sehen, wie ich es mag. Danke Kevin! – jlembke

+0

Das ist in Ordnung, wenn es für Sie funktioniert. Eine Alternative besteht darin, das Domänenmodell auf einer niedrigeren Ebene als die Domänendienste zu halten. Es fühlt sich an wie eine bessere Trennung von Bedenken und hilft beim Testen. Ich benutze sehr gerne Jeffrey Palermos Zwiebelarchitektur. Sie sollten es überprüfen. http://jeffreypalermo.com/blog/the-onion-architecture-part-1/ –

+0

Die Domänen-Entitäten sollten in der Lage sein, diese Geschäftsanforderungen zu erfüllen, ohne eine spezielle Injektion von Schnittstellen, Prädikaten usw. zu erfordern.Der Schlüssel ist, dass Ihre Repositorys Objekte zurückgeben, die genau auf die Rolle zugeschnitten sind, für die sie gerade benötigt werden. Das befreit Ihren Dienst- und Anwendungscode vollständig davon, dass Sie alles über die Domänenlogik wissen müssen, die vollständig in Ihren Domänenentitäten eingekapselt ist. So wird Ihr Code viel klarer in seiner Absicht, ohne eine Ladung unordentliches Gepäck. –

1

Zusätzlich zu dem Problem, den "Pool" -Wert zu bekommen (wo ich den Wert mit einer Methode auf einem OrderRepository abfragen würde), haben Sie die Sperren Auswirkungen für dieses Problem in Betracht gezogen?

Wenn sich der "Pool" ständig ändert, gibt es eine Chance, dass sich jemandes Transaktion einschleicht, kurz nachdem Ihre Regel vorüber ist, aber kurz bevor Sie Ihre Änderungen an der db vornehmen?

Eric Evans bezieht sich auf dieses Problem in Kapitel 6 seines Buches ("Aggregates").

+0

Vijay, Sie sind zu 100% korrekt. Dies ist das nächste Problem, bei dem wir den Schlaf verlieren. Ich habe diesen Teil des Buches vermisst. Ich werde das jetzt lesen !! – jlembke