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 ;-)
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
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. –
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 ;-) –