Ich passe das domänengetriebene Design seit ungefähr 8 Jahren an und selbst nach all den Jahren gibt es immer noch eine Sache, die mich nervt. Das heißt, es wird nach einem eindeutigen Datensatz im Datenspeicher für ein Domänenobjekt gesucht.
Im September 2013 erwähnte Martin Fowler das TellDon'tAsk-Prinzip , das nach Möglichkeit auf alle Domänenobjekte angewendet werden sollte, die dann eine Nachricht über den Ablauf der Operation zurückgeben sollten (im objektorientierten Design erfolgt dies meistens durch Ausnahmen, wenn die Operation war nicht erfolgreich).
Meine Projekte sind normalerweise in viele Teile unterteilt, von denen zwei Domain (mit Geschäftsregeln und nichts anderem, die Domain ist völlig persistenzunabhängig) und Services sind. Dienste, die sich mit der Repository-Schicht auskennen, die für CRUD-Daten verwendet wird.
Da die Eindeutigkeit eines zu einem Objekt gehörenden Attributs eine Domänen- / Geschäftsregel ist, sollte das Domänenmodul lang sein, damit die Regel genau dort ist, wo sie sein soll.
Um die Eindeutigkeit eines Datensatzes überprüfen zu können, müssen Sie den aktuellen Datensatz, normalerweise eine Datenbank, abfragen, um herauszufinden, ob bereits ein anderer Datensatz mit einem Beispiel Name
vorhanden ist.
Wenn man bedenkt, dass die Domänenschicht die Persistenz nicht kennt und keine Ahnung hat, wie die Daten abgerufen werden sollen, sondern nur, wie sie verarbeitet werden sollen, kann sie die Repositorys selbst nicht wirklich berühren.
Das Design, das ich damals angepasst habe, sieht folgendermaßen aus:
class ProductRepository
{
// throws Repository.RecordNotFoundException
public Product GetBySKU(string sku);
}
class ProductCrudService
{
private ProductRepository pr;
public ProductCrudService(ProductRepository repository)
{
pr = repository;
}
public void SaveProduct(Domain.Product product)
{
try {
pr.GetBySKU(product.SKU);
throw Service.ProductWithSKUAlreadyExistsException("msg");
} catch (Repository.RecordNotFoundException e) {
// suppress/log exception
}
pr.MarkFresh(product);
pr.ProcessChanges();
}
}
Dies führt dazu, dass Dienste Domänenregeln definieren und nicht die Domänenschicht selbst, und dass die Regeln auf mehrere Abschnitte Ihres Codes verteilt sind.
Ich habe das TellDon'tAsk-Prinzip erwähnt, da der Dienst, wie Sie deutlich sehen können, eine Aktion anbietet (er speichert entweder die Product
Ausnahme oder löst eine Ausnahme aus), aber innerhalb der Methode arbeiten Sie Objekte mithilfe eines prozeduralen Ansatzes.
Die naheliegende Lösung besteht darin, eine Domain.ProductCollection
Klasse mit einer Add(Domain.Product)
Methode zu erstellen , die ProductWithSKUAlreadyExistsException
das auslöst. Die Leistung ist jedoch sehr gering, da Sie alle Produkte aus dem Datenspeicher abrufen müssten, um im Code herauszufinden, ob ein Produkt bereits dieselbe SKU hat als das Produkt, das Sie hinzufügen möchten.
Wie löst ihr dieses spezielle Problem? Dies ist an sich kein wirkliches Problem. Ich habe die Service-Schicht jahrelang bestimmte Domain-Regeln darstellen lassen. Die Service-Schicht dient normalerweise auch komplexeren Domain-Operationen. Ich frage mich nur, ob Sie während Ihrer Karriere auf eine bessere, zentralere Lösung gestoßen sind.
Antworten:
Ich würde diesem Teil nicht zustimmen. Besonders der letzte Satz.
Es ist zwar richtig, dass die Domäne persistenzunabhängig sein sollte, sie weiß jedoch, dass es eine "Sammlung von Domänenentitäten" gibt. Und dass es Domain-Regeln gibt, die diese Sammlung als Ganzes betreffen. Einzigartigkeit ist einer von ihnen. Und da die Implementierung der eigentlichen Logik stark vom spezifischen Persistenzmodus abhängt, muss es in der Domäne eine Art Abstraktion geben, die die Notwendigkeit dieser Logik spezifiziert.
Es ist also so einfach wie das Erstellen einer Schnittstelle, die abfragen kann, ob der Name bereits vorhanden ist. Diese wird dann in Ihrem Datenspeicher implementiert und von jedem aufgerufen, der wissen muss, ob der Name eindeutig ist.
Und ich möchte betonen, dass Repositories DOMAIN-Dienste sind. Sie sind Abstraktionen rund um die Beharrlichkeit. Es ist die Implementierung des Repositorys, die von der Domäne getrennt sein sollte. Es ist absolut nichts Falsches daran, wenn eine Domänenentität einen Domänendienst aufruft. Es ist nichts Falsches daran, dass eine Entität das Repository verwenden kann, um eine andere Entität abzurufen oder bestimmte Informationen abzurufen, die nicht ohne weiteres im Speicher gespeichert werden können. Dies ist ein Grund, warum Repository das Schlüsselkonzept in Evans 'Buch ist .
quelle
Domain.ProductCollection
ich mir vorgestellt habe, wenn man bedenkt, dass sie für das Abrufen von Objekten aus der Domänenschicht verantwortlich sind?Domain.ProductCollection
sondern stattdessen das Repository zu fragen, ebenso wie zu fragen, ob dasDomain.ProductCollection
ein Produkt mit dem enthält Diesmal wurde die SKU übergeben und stattdessen das Repository gefragt (dies ist eigentlich das Beispiel), das die zugrunde liegende Datenbank abfragt, anstatt über die vorinstallierten Produkte zu iterieren. Ich möchte nicht alle Produkte im Speicher speichern, es sei denn, ich muss, dies wäre ein völliger Unsinn.IProductRepository
Schnittstelle mit habenDoesNameExist
, ist es offensichtlich, was die Methode tun sollte. Es sollte jedoch irgendwo in der Domäne sein, sicherzustellen, dass das Produkt nicht denselben Namen wie das vorhandene Produkt haben kann.Sie müssen Greg Young bei der Set-Validierung lesen .
Kurze Antwort: Bevor Sie zu weit in das Rattennest vordringen, müssen Sie sicherstellen, dass Sie den Wert der Anforderung aus geschäftlicher Sicht verstehen. Wie teuer ist es wirklich, die Duplizierung zu erkennen und zu mindern, anstatt sie zu verhindern?
Längere Antwort: Ich habe eine Reihe von Möglichkeiten gesehen, aber alle haben Kompromisse.
Sie können nach Duplikaten suchen, bevor Sie den Befehl an die Domäne senden. Dies kann im Client oder im Service erfolgen (Ihr Beispiel zeigt die Technik). Wenn Sie mit der Logik, die aus der Domänenschicht austritt, nicht zufrieden sind, können Sie mit a das gleiche Ergebnis erzielen
DomainService
.Auf diese Weise muss die Implementierung des DeduplicationService natürlich etwas über das Nachschlagen des vorhandenen Skus wissen. Während also ein Teil der Arbeit in die Domäne zurückgedrängt wird, stehen Sie immer noch vor denselben grundlegenden Problemen (Sie benötigen eine Antwort für die Satzvalidierung, Probleme mit den Rennbedingungen).
Sie können die Validierung in Ihrer Persistenzschicht selbst durchführen. Relationale Datenbanken sind wirklich gut in der Satzvalidierung. Legen Sie eine Einschränkung für die Eindeutigkeit der SKU-Spalte Ihres Produkts fest, und Sie können loslegen. Die Anwendung speichert das Produkt nur im Repository, und bei einem Problem tritt eine Einschränkungsverletzung auf. Der Anwendungscode sieht also gut aus und Ihre Rennbedingung ist beseitigt, aber es sind "Domain" -Regeln durchgesickert.
Sie können in Ihrer Domain ein separates Aggregat erstellen, das den Satz bekannter Skus darstellt. Ich kann mir hier zwei Variationen vorstellen.
Eines ist so etwas wie ein ProductCatalog; Produkte existieren woanders, aber die Beziehung zwischen Produkten und Skus wird durch einen Katalog aufrechterhalten, der die Einzigartigkeit von Skus garantiert. Nicht, dass dies impliziert, dass Produkte keinen Skus haben. Skus werden von einem ProductCatalog zugewiesen (wenn Skus eindeutig sein soll, erreichen Sie dies, indem Sie nur ein einziges ProductCatalog-Aggregat haben). Überprüfen Sie die allgegenwärtige Sprache mit Ihren Domain-Experten. Wenn so etwas existiert, könnte dies der richtige Ansatz sein.
Eine Alternative ist eher ein Sku-Reservierungsservice. Der grundlegende Mechanismus ist der gleiche: Ein Aggregat kennt alle Skus und kann so die Einführung von Duplikaten verhindern. Der Mechanismus ist jedoch etwas anders: Sie erwerben einen Leasingvertrag für einen Sku, bevor Sie ihn einem Produkt zuweisen. Wenn Sie das Produkt erstellen, geben Sie den Mietvertrag an die SKU weiter. Es gibt immer noch eine Rennbedingung im Spiel (verschiedene Aggregate, daher unterschiedliche Transaktionen), aber es hat einen anderen Geschmack. Der eigentliche Nachteil hierbei ist, dass Sie einen Leasingdienst in das Domain-Modell projizieren, ohne wirklich eine Begründung in der Domain-Sprache zu haben.
Sie können alle Produktentitäten in einem einzigen Aggregat zusammenfassen, dh im oben beschriebenen Produktkatalog. Sie erhalten absolut die Einzigartigkeit des Skus, wenn Sie dies tun, aber die Kosten sind zusätzliche Streitigkeiten. Das Ändern eines Produkts bedeutet wirklich das Ändern des gesamten Katalogs.
Vielleicht musst du nicht. Wenn Sie Ihren Sku mit einem Bloom-Filter testen , können Sie viele einzigartige Skus entdecken, ohne das Set zu laden.
Wenn Ihr Anwendungsfall es Ihnen erlaubt, willkürlich zu entscheiden, welchen Skus Sie ablehnen, können Sie alle Fehlalarme entfernen (keine große Sache, wenn Sie den Clients erlauben, den von ihnen vorgeschlagenen Skus zu testen, bevor Sie den Befehl senden). Auf diese Weise können Sie vermeiden, dass das Gerät in den Speicher geladen wird.
(Wenn Sie mehr Akzeptanz wünschen, können Sie in Betracht ziehen, den Skus im Falle einer Übereinstimmung im Bloom-Filter faul zu laden. Manchmal riskieren Sie immer noch, den gesamten Skus in den Speicher zu laden, aber dies sollte nicht der Fall sein, wenn Sie dies zulassen den Client-Code, um den Befehl vor dem Senden auf Fehler zu überprüfen).
quelle