In Domain Driven Design scheint es viele von Vereinbarung zu geben, dass Entitäten nicht direkt auf Repositorys zugreifen sollten.
Kam dies von Eric Evans Domain Driven Design Buch, oder kam es von woanders?
Wo gibt es einige gute Erklärungen für die Argumentation dahinter?
bearbeiten: Zur Verdeutlichung: Ich spreche nicht von der klassischen OO Praxis, den Datenzugriff von der Geschäftslogik in eine separate Ebene zu trennen soll überhaupt mit der Datenzugriffsebene sprechen (dh sie sollen keine Verweise auf Repository-Objekte enthalten)
update: Ich habe BacceSR das Kopfgeld gegeben, weil seine Antwort am nächsten schien, aber ich bin immer noch ziemlich im Dunkeln. Wenn es ein so wichtiges Prinzip ist, sollte es doch irgendwo online gute Artikel darüber geben?
update: März 2013, die Upvotes zu der Frage lassen darauf schließen, dass großes Interesse besteht. Auch wenn es viele Antworten gibt, denke ich, dass es noch Platz für mehr gibt, wenn die Leute Ideen dazu haben.
Hier herrscht eine gewisse Verwirrung. Repositorys greifen auf aggregierte Roots zu. Aggregierte Wurzeln sind Entitäten. Der Grund dafür ist die Trennung von Bedenken und eine gute Schichtung. Dies ist bei kleinen Projekten nicht sinnvoll. Wenn Sie jedoch zu einem großen Team gehören, möchten Sie sagen: "Sie greifen über das Produkt-Repository auf ein Produkt zu. Produkt ist ein aggregierter Stamm für eine Sammlung von Entitäten, einschließlich des ProductCatalog-Objekts. Wenn Sie den ProductCatalog aktualisieren möchten, müssen Sie das ProductRepository durchgehen. "
Auf diese Weise haben Sie eine sehr, sehr klare Trennung in der Geschäftslogik und wo die Dinge aktualisiert werden. Sie haben kein Kind, das alleine unterwegs ist und dieses gesamte Programm schreibt, das all diese komplizierten Dinge mit dem Produktkatalog zu tun hat, und wenn es darum geht, es in das vorgelagerte Projekt zu integrieren, sitzen Sie da und schauen es sich an und realisieren es alles muss weggeworfen werden. Es bedeutet auch, dass Mitarbeiter, die dem Team beitreten, neue Funktionen hinzufügen, wissen, wohin sie gehen müssen und wie sie das Programm strukturieren müssen.
Aber warte! Repository bezieht sich auch auf die Persistenzschicht, wie im Repository-Muster. In einer besseren Welt würden das Repository von Eric Evans und das Repository-Muster unterschiedliche Namen haben, da sie sich häufig überlappen. Um das Repository-Muster zu erhalten, haben Sie einen Kontrast zu anderen Arten des Datenzugriffs, beispielsweise mit einem Servicebus oder einem Ereignismodellsystem. In der Regel bleibt die Definition des Eric Evans-Repository auf der Strecke und Sie fangen an, über einen begrenzten Kontext zu sprechen. Jeder begrenzte Kontext ist im Wesentlichen seine eigene Anwendung. Möglicherweise verfügen Sie über ein ausgeklügeltes Genehmigungssystem, um Dinge in den Produktkatalog aufzunehmen. In Ihrem ursprünglichen Design stand das Produkt im Mittelpunkt, in diesem begrenzten Kontext ist es jedoch der Produktkatalog. Sie können weiterhin über einen Servicebus auf Produktinformationen zugreifen und Produkte aktualisieren, müssen sich jedoch darüber im Klaren sein, dass ein Produktkatalog außerhalb des begrenzten Kontexts möglicherweise etwas völlig anderes bedeutet.
Zurück zu deiner ursprünglichen Frage. Wenn Sie von einer Entität aus auf ein Repository zugreifen, bedeutet dies, dass es sich bei der Entität nicht um eine Geschäftsentität handelt, sondern wahrscheinlich um eine Entität, die in einer Serviceschicht vorhanden sein sollte. Dies liegt daran, dass Entitäten Geschäftsobjekte sind und sich darum bemühen sollten, einer DSL (domänenspezifischen Sprache) so ähnlich wie möglich zu sein. Haben Sie nur Geschäftsinformationen in dieser Schicht. Wenn Sie ein Leistungsproblem beheben, sollten Sie sich anderswo umsehen, da hier nur Geschäftsinformationen angezeigt werden sollten. Wenn plötzlich Anwendungsprobleme auftreten, ist es sehr schwierig, eine Anwendung zu erweitern und zu warten. Dies ist das eigentliche Herzstück von DDD: die Erstellung wartungsfähiger Software.
Antwort auf Kommentar 1: Richtig, gute Frage. Daher findet nicht alle Validierung in der Domänenschicht statt. Sharp hat ein Attribut "DomainSignature", das genau das tut, was Sie wollen. Es ist persistenzbewusst, aber ein Attribut zu sein, hält die Domänenschicht sauber. Es stellt sicher, dass Sie keine doppelte Entität mit demselben Namen in Ihrem Beispiel haben.
Aber lassen Sie uns über kompliziertere Validierungsregeln sprechen. Angenommen, Sie sind Amazon.com. Haben Sie jemals etwas mit einer abgelaufenen Kreditkarte bestellt? Ich habe, wo ich die Karte nicht aktualisiert und etwas gekauft habe. Es nimmt die Bestellung an und die Benutzeroberfläche informiert mich, dass alles pfirsichfarben ist. Etwa 15 Minuten später erhalte ich eine E-Mail mit dem Hinweis, dass bei meiner Bestellung ein Problem vorliegt. Meine Kreditkarte ist ungültig. Was hier passiert, ist, dass es im Idealfall eine Regex-Validierung in der Domänenschicht gibt. Ist das eine korrekte Kreditkartennummer? Wenn ja, die Bestellung fortsetzen. Es gibt jedoch eine zusätzliche Validierung auf der Ebene der Anwendungstasks, bei der ein externer Dienst abgefragt wird, um festzustellen, ob die Zahlung mit der Kreditkarte möglich ist. Wenn nicht, versenden Sie nichts, setzen Sie die Bestellung aus und warten Sie auf den Kunden. Dies sollte alles in einer Serviceschicht erfolgen.
Haben Sie keine Angst davor, Validierungsobjekte auf der Serviceebene zu erstellen, die auf Repositorys zugreifen können . Halten Sie es einfach aus der Domain-Schicht heraus.
Zuerst war ich der Überzeugung, einigen meiner Entitäten Zugriff auf Repositories zu gewähren (dh verzögertes Laden ohne ORM). Später kam ich zu dem Schluss, dass ich nicht sollte und dass ich alternative Wege finden könnte:
Vernon Vaughn in dem roten Buch Implementing Domain-Driven Design bezieht sich an zwei mir bekannten Stellen auf dieses Problem (Anmerkung: Dieses Buch wird von Evans voll befürwortet wie Sie im Vorwort lesen können). In Kapitel 7 zu Diensten verwendet er einen Domänendienst und eine Spezifikation, um die Notwendigkeit zu umgehen, dass ein Aggregat ein Repository verwendet und ein anderes Aggregat, um festzustellen, ob ein Benutzer authentifiziert ist. Er wird mit den Worten zitiert:
Als Faustregel sollten wir versuchen, die Verwendung von Repositorys (12) innerhalb von Aggregaten zu vermeiden, wenn dies möglich ist.
Vernon, Vaughn (06.02.2013). Implementieren von domänengesteuertem Design (Kindle Location 6089). Pearson Ausbildung. Kindle Edition.
Und in Kapitel 10 über Aggregate im Abschnitt mit dem Titel "Modellnavigation" sagt er (kurz nachdem er die Verwendung globaler eindeutiger IDs zum Verweisen auf andere Aggregatwurzeln empfohlen hat):
Identitätsbezug verhindert die Navigation durch das Modell nicht vollständig. Einige verwenden ein Repository (12) aus einem Aggregat zum Nachschlagen. Diese Technik wird als "Disconnected Domain Model" bezeichnet und ist eigentlich eine Form des verzögerten Ladens. Es wird jedoch ein anderer Ansatz empfohlen: Verwenden Sie ein Repository oder einen Domänendienst (7), um abhängige Objekte zu suchen, bevor Sie das Aggregatverhalten aufrufen. Ein Client-Anwendungsdienst kann dies steuern und dann an das Aggregat senden:
Er zeigt ein Beispiel dafür im Code:
public class ProductBacklogItemService ... {
...
@Transactional
public void assignTeamMemberToTask(
String aTenantId,
String aBacklogItemId,
String aTaskId,
String aTeamMemberId) {
BacklogItem backlogItem = backlogItemRepository.backlogItemOfId(
new TenantId( aTenantId),
new BacklogItemId( aBacklogItemId));
Team ofTeam = teamRepository.teamOfId(
backlogItem.tenantId(),
backlogItem.teamId());
backlogItem.assignTeamMemberToTask(
new TeamMemberId( aTeamMemberId),
ofTeam,
new TaskId( aTaskId));
}
...
}
Er erwähnt auch noch eine andere Lösung, wie ein Domänendienst in einer Aggregatbefehlsmethode zusammen mit double-dispatch verwendet werden kann. (Ich kann nicht genug empfehlen, wie nützlich es ist, sein Buch zu lesen. Nachdem Sie es satt haben, endlos im Internet zu stöbern, stöbern Sie in dem wohlverdienten Geld und lesen Sie das Buch.)
Ich hatte dann einige Diskussion mit dem immer gnädigen Marco Pivetta @ Ocramius , der mir ein bisschen Code zeigte, wie ich eine Spezifikation aus der Domain herausholte und diese verwendete:
1) Dies wird nicht empfohlen:
$user->mountFriends(); // <-- has a repository call inside that loads friends?
2) In einem Domain-Service ist dies gut:
public function mountYourFriends(MountFriendsCommand $mount) { /* see http://store.steampowered.com/app/296470/ */
$user = $this->users->get($mount->userId());
$friends = $this->users->findBySpecification($user->getFriendsSpecification());
array_map([$user, 'mount'], $friends);
}
Das ist eine sehr gute Frage. Ich freue mich auf eine Diskussion darüber. Aber ich denke, es wird in mehreren DDD-Büchern und Jimmy Nilsons und Eric Evans erwähnt. Ich denke, es ist auch an Beispielen zu erkennen, wie man das Reposistory-Muster verwendet.
ABER lasst uns diskutieren. Ich denke, ein sehr berechtigter Gedanke ist, warum eine Entität wissen sollte, wie sie eine andere Entität bestehen kann. Wichtig bei DDD ist, dass jede Entität die Verantwortung hat, ihre eigene "Wissenssphäre" zu verwalten und nichts darüber zu wissen, wie sie andere Entitäten lesen oder schreiben soll. Sicher, Sie können Entität A wahrscheinlich nur um eine Repository-Schnittstelle zum Lesen von Entitäten B erweitern. Das Risiko besteht jedoch darin, dass Sie wissen, wie Sie B beibehalten können. Führt Entität A auch eine Validierung für B durch, bevor Sie B in db beibehalten?
Wie Sie sehen, kann Entität A stärker in den Lebenszyklus von Entität B einbezogen werden, was dem Modell mehr Komplexität verleihen kann.
Ich denke (ohne Beispiel), dass Unit-Tests komplexer werden.
Aber ich bin mir sicher, dass es immer Szenarien geben wird, in denen Sie versucht sind, Repositorys über Entitäten zu verwenden. Sie müssen sich jedes Szenario ansehen, um ein gültiges Urteil zu fällen. Vor-und Nachteile. Aber die Repository-Entity-Lösung fängt meiner Meinung nach mit vielen Nachteilen an. Es muss ein ganz besonderes Szenario mit Profis sein, die die Nachteile ausgleichen.
Die ersten beiden Seiten des Kapitels "Model Driven Design" geben meiner Ansicht nach eine Rechtfertigung dafür, warum Sie technische Implementierungsdetails von der Implementierung des Domänenmodells abstrahieren möchten.
Dies scheint alles zu dem Zweck zu sein, ein separates "Analysemodell" zu vermeiden, das von der tatsächlichen Implementierung des Systems getrennt wird.
Soweit ich das Buch verstehe, heißt es, dass dieses "Analysemodell" ohne Berücksichtigung der Softwareimplementierung entworfen werden kann. Sobald Entwickler versuchen, das von der Geschäftsseite verstandene Modell zu implementieren, bilden sie aus Gründen der Notwendigkeit ihre eigenen Abstraktionen, was zu einer Wand in der Kommunikation und im Verständnis führt.
In der anderen Richtung können Entwickler, die zu viele technische Probleme in das Domänenmodell einbringen, ebenfalls zu dieser Kluft führen.
Sie können also davon ausgehen, dass das Üben einer Trennung von Bedenken wie z. B. der Persistenz dazu beitragen kann, ein abweichendes Analysemodell vor diesem Design zu schützen. Wenn es notwendig erscheint, Dinge wie Persistenz in das Modell einzuführen, ist dies eine rote Fahne. Vielleicht ist das Modell für die Implementierung nicht praktisch.
Zitat:
"Das einzelne Modell verringert die Fehlerwahrscheinlichkeit, da das Design nun ein direktes Ergebnis des sorgfältig überlegten Modells ist. Das Design und sogar der Code selbst haben die Kommunikationsfähigkeit eines Modells."
So wie ich das interpretiere, wenn Sie am Ende mehr Codezeilen haben, die sich mit Dingen wie dem Datenbankzugriff befassen, verlieren Sie diese Kommunikationsfähigkeit.
Wenn Sie auf eine Datenbank zugreifen müssen, um beispielsweise die Eindeutigkeit zu überprüfen, schauen Sie sich Folgendes an:
Udi Dahan: Die größten Fehler, die Teams bei der Anwendung von DDD machen
http://gojko.net/2010/06/11/udi-dahan-the-biggest-mistakes-teams-make-when-applying-ddd/
unter "Alle Regeln sind nicht gleich"
und
Verwenden des Domänenmodellmusters
http://msdn.Microsoft.com/en-us/magazine/ee236415.aspx#id0400119
klicken Sie unter "Szenarien für die Nichtverwendung des Domänenmodells" auf das gleiche Thema.
Die "Datenzugriffsebene" wurde über eine Schnittstelle abstrahiert, die Sie aufrufen, um die erforderlichen Daten abzurufen:
var orderLines = OrderRepository.GetOrderLines(orderId);
foreach (var line in orderLines)
{
total += line.Price;
}
Vorteile: Die Benutzeroberfläche trennt den Installationscode für den Datenzugriff, sodass Sie weiterhin Tests schreiben können. Der Datenzugriff kann von Fall zu Fall abgewickelt werden und bietet eine bessere Leistung als eine generische Strategie.
Nachteile: Der aufrufende Code muss annehmen, was geladen wurde und was nicht.
Angenommen, GetOrderLines gibt OrderLine-Objekte aus Leistungsgründen mit der Eigenschaft ProductInfo null zurück. Der Entwickler muss den Code hinter der Schnittstelle genau kennen.
Ich habe diese Methode auf realen Systemen ausprobiert. Am Ende ändern Sie den Umfang dessen, was ständig geladen wird, um Leistungsprobleme zu beheben. Am Ende schauen Sie hinter die Benutzeroberfläche und sehen sich den Datenzugriffscode an, um zu sehen, was geladen wird und was nicht.
Die Trennung von Bedenken sollte es dem Entwickler nun ermöglichen, sich so weit wie möglich gleichzeitig auf einen Aspekt des Codes zu konzentrieren. Die Schnittstellentechnik entfernt das WIE werden diese Daten geladen, aber nicht WIE VIEL Daten werden geladen, WANN werden sie geladen und WO werden sie geladen?.
Fazit: Ziemlich geringer Abstand!
Daten werden bei Bedarf geladen. Aufrufe zum Laden von Daten werden im Objektdiagramm selbst ausgeblendet. Wenn Sie auf eine Eigenschaft zugreifen, wird möglicherweise eine SQL-Abfrage ausgeführt, bevor das Ergebnis zurückgegeben wird.
foreach (var line in order.OrderLines)
{
total += line.Price;
}
Vorteile: Das WANN, WO und WIE des Datenzugriffs ist dem Entwickler verborgen, der sich auf die Domänenlogik konzentriert. Das Aggregat enthält keinen Code, der sich mit dem Laden von Daten befasst. Die geladene Datenmenge kann die genaue Menge sein, die der Code erfordert.
Nachteile: Wenn Sie mit einem Leistungsproblem konfrontiert sind, ist es schwierig, das Problem zu beheben, wenn Sie eine allgemeine Lösung mit dem Titel "Einheitsgröße" haben. Ein verzögertes Laden kann insgesamt zu einer schlechteren Leistung führen, und das Implementieren eines verzögerten Ladens kann schwierig sein.
Jeder Anwendungsfall wird durch ein Rolleninterface explizit dargestellt, das von der Aggregatklasse implementiert wird, sodass Datenladestrategien pro Anwendungsfall behandelt werden können.
Die Abrufstrategie könnte folgendermaßen aussehen:
public class BillOrderFetchingStrategy : ILoadDataFor<IBillOrder, Order>
{
Order Load(string aggregateId)
{
var order = new Order();
order.Data = GetOrderLinesWithPrice(aggregateId);
return order;
}
}
Dann kann Ihr Aggregat so aussehen:
public class Order : IBillOrder
{
void BillOrder(BillOrderCommand command)
{
foreach (var line in this.Data.OrderLines)
{
total += line.Price;
}
etc...
}
}
Die BillOrderFetchingStrategy wird verwendet, um das Aggregat zu erstellen, und dann erledigt das Aggregat seine Arbeit.
Vorteile: Ermöglicht benutzerdefinierten Code pro Anwendungsfall, wodurch eine optimale Leistung erzielt wird. Entspricht dem Prinzip der Schnittstellentrennung . Keine komplexen Code-Anforderungen. Aggregate-Unit-Tests müssen nicht die Ladestrategie imitieren. In den meisten Fällen kann eine generische Ladestrategie verwendet werden (z. B. eine "Lade alle" -Strategie), und bei Bedarf können spezielle Ladestrategien implementiert werden.
Nachteile: Der Entwickler muss die Abrufstrategie nach einer Änderung des Domain-Codes noch anpassen/überprüfen.
Beim Abrufstrategieansatz werden Sie möglicherweise immer noch den benutzerdefinierten Abrufcode ändern, um die Geschäftsregeln zu ändern. Es ist keine perfekte Trennung von Bedenken, aber es wird am Ende wartbarer und ist besser als die erste Option. Die Abrufstrategie kapselt das WIE, WANN und WO Daten geladen werden. Es hat eine bessere Trennung von Bedenken, ohne an Flexibilität zu verlieren, wie die Einheitsgröße für alle langsamen Ladevorgänge.
Ich fand, dass dieser Blog ziemlich gute Argumente gegen das Einkapseln von Repositories in Entities hat:
http://thinkbeforecoding.com/post/2009/03/04/How-not-to-inject-services-in-entities
Was für eine ausgezeichnete Frage. Ich bin auf dem gleichen Weg der Entdeckung und die meisten Antworten im Internet scheinen so viele Probleme zu bringen, wie sie Lösungen bringen.
Also (auf die Gefahr hin, etwas zu schreiben, mit dem ich in einem Jahr nicht einverstanden bin), hier sind meine bisherigen Entdeckungen.
Zuallererst mögen wir ein Rich Domain Model, das uns hohe Auffindbarkeit (von dem, was wir mit einem Aggregat machen können) und Lesbarkeit ( ausdrucksstarke Methodenaufrufe).
// Entity
public class Invoice
{
...
public void SetStatus(StatusCode statusCode, DateTime dateTime) { ... }
public void CreateCreditNote(decimal amount) { ... }
...
}
Wir möchten dies erreichen, ohne dem Konstruktor einer Entität Services hinzuzufügen, weil:
Wie können wir das dann tun? Meine Schlussfolgerung bisher ist, dass Methodenabhängigkeiten und Doppelversand eine anständige Lösung bieten.
public class Invoice
{
...
// Simple method injection
public void SetStatus(IInvoiceLogger logger, StatusCode statusCode, DateTime dateTime)
{ ... }
// Double dispatch
public void CreateCreditNote(ICreditNoteService creditNoteService, decimal amount)
{
creditNoteService.CreateCreditNote(this, amount);
}
...
}
CreateCreditNote()
erfordert jetzt einen Dienst, der für die Erstellung von Gutschriften zuständig ist. Es verwendet Doppelversand, vollständig Auslagerung der Arbeit an den zuständigen Dienst, während Auffindbarkeit aufrechterhalten von der Entität Invoice
.
SetStatus()
hat jetzt eine einfache Abhängigkeit von einem Logger, der offensichtlich einen Teil der Arbeit ausführen wird .
Um den Client-Code zu vereinfachen, können wir uns stattdessen über ein IInvoiceService
anmelden. Schließlich scheint die Rechnungserfassung einer Rechnung ziemlich eigen zu sein. Ein solches einzelnes IInvoiceService
vermeidet die Notwendigkeit aller Arten von Minidiensten für verschiedene Operationen. Der Nachteil ist, dass es dunkel wird, was genau dieser Dienst tun wird . Es könnte sogar anfangen wie doppelter Versand auszusehen , während der größte Teil der Arbeit noch in SetStatus()
selbst erledigt wird.
Wir könnten den Parameter immer noch "Logger" nennen, in der Hoffnung, unsere Absicht zu enthüllen. Scheint allerdings etwas schwach.
Stattdessen würde ich nach einem IInvoiceLogger
fragen (wie wir es bereits im Codebeispiel tun) und IInvoiceService
diese Schnittstelle implementieren lassen. Der Client-Code kann einfach seine einzelnen IInvoiceService
für alle Invoice
Methoden verwenden, die nach einem solchen speziellen, rechnungsinternen "Mini-Service" fragen, während die Methodensignaturen immer noch deutlich machen, was passiert sie fragen nach.
Ich stelle fest, dass ich nicht Repositorys explizit angesprochen habe. Nun, der Logger ist oder benutzt ein Repository, aber lassen Sie mich auch ein expliziteres Beispiel geben. Wir können den gleichen Ansatz verwenden, wenn das Repository nur in ein oder zwei Methoden benötigt wird.
public class Invoice
{
public IEnumerable<CreditNote> GetCreditNotes(ICreditNoteRepository repository)
{ ... }
}
In der Tat bietet dies eine Alternative zu den immer lästigen faulen Lasten.
pdate: Ich habe den folgenden Text aus historischen Gründen hinterlassen, schlage aber vor, faulen Lasten zu 100% aus dem Weg zu gehen.
Für echte, eigenschaftsbasierte verzögerte Ladevorgänge verwende ich derzeit die Konstruktorinjektion, jedoch auf persistenzunabhängige Weise.
public class Invoice
{
// Lazy could use an interface (for contravariance if nothing else), but I digress
public Lazy<IEnumerable<CreditNote>> CreditNotes { get; }
// Give me something that will provide my credit notes
public Invoice(Func<Invoice, IEnumerable<CreditNote>> lazyCreditNotes)
{
this.CreditNotes = new Lazy<IEnumerable<CreditNotes>>() => lazyCreditNotes(this));
}
}
Einerseits kann ein Repository, das ein Invoice
aus der Datenbank lädt, freien Zugriff auf eine Funktion haben, die die entsprechenden Gutschriften lädt und diese Funktion in das Invoice
einfügt.
Andererseits übergibt Code, der ein aktuelles neues Invoice
erstellt, lediglich eine Funktion, die eine leere Liste zurückgibt:
new Invoice(inv => new List<CreditNote>() as IEnumerable<CreditNote>)
(Ein Brauch ILazy<out T>
Könnte uns von der hässlichen Besetzung von IEnumerable
befreien, aber das würde die Diskussion erschweren.)
// Or just an empty IEnumerable
new Invoice(inv => IEnumerable.Empty<CreditNote>())
Ich würde mich freuen, Ihre Meinungen, Vorlieben und Verbesserungen zu hören!
Für mich scheint dies eine allgemein gute OOD-bezogene Praxis zu sein, anstatt spezifisch für DDD zu sein.
Gründe, die mir einfallen, sind:
vernon Vaughn gibt einfach eine Lösung:
Verwenden Sie einen Repository- oder Domänendienst, um abhängige Objekte zu suchen, bevor Sie das Aggregatverhalten aufrufen. Ein Clientanwendungsdienst kann dies steuern.
Ich habe gelernt, objektorientierte Programmierung zu programmieren, bevor all diese unterschiedlichen Layer-Summen auftauchen und meine ersten Objekte/Klassen DID direkt der Datenbank zugeordnet werden.
Schließlich habe ich eine Zwischenebene hinzugefügt, da ich auf einen anderen Datenbankserver migrieren musste. Ich habe mehrmals dasselbe Szenario gesehen/gehört.
Ich denke, die Trennung des Datenzugriffs (a.k.a. "Repository") von Ihrer Geschäftslogik ist eines der Dinge, die mehrfach neu erfunden wurden, obwohl das Buch "Domain Driven Design" viel "Lärm" verursacht.
Ich benutze derzeit 3 Ebenen (GUI, Logik, Datenzugriff), wie viele Entwickler, weil es eine gute Technik ist.
Trennen der Daten in eine Repository
Ebene (a.k.a. Data Access
layer), kann als eine gute Programmiertechnik angesehen werden, nicht nur als eine Regel, der zu folgen ist.
Wie bei vielen Methoden möchten Sie möglicherweise Ihr Programm starten, indem Sie es NICHT implementieren, und schließlich aktualisieren, sobald Sie sie verstanden haben.
Zitat: Die Ilias wurde nicht vollständig von Homer erfunden, Carmina Burana wurde nicht vollständig von Carl Orff erfunden, und in beiden Fällen bekam die Person, die anderen Arbeit geleistet hat, alle zusammen den Verdienst ;-)
Um Carolina Lilientahl zu zitieren: "Muster sollten Zyklen verhindern" https://www.youtube.com/watch?v=eJjadzMRQAk , wobei sie sich auf zyklische Abhängigkeiten zwischen Klassen bezieht. Im Fall von Repositorys in Aggregaten besteht die Versuchung, zyklische Abhängigkeiten zu erstellen, da die Objektnavigation nur aus Gründen der Zweckmäßigkeit möglich ist. Das oben von prograhammer erwähnte Muster, das von Vernon Vaughn empfohlen wurde, wobei andere Aggregate durch IDs anstelle von Stamminstanzen referenziert werden (gibt es einen Namen für dieses Muster?), Schlägt eine Alternative vor, die möglicherweise zu anderen Lösungen führt.
Beispiel für die zyklische Abhängigkeit zwischen Klassen (Geständnis):
(Time0): Zwei Klassen, Sample und Well, verweisen aufeinander (zyklische Abhängigkeit). "Well" bezieht sich auf "Sample" und "Sample" bezieht sich aus Bequemlichkeitsgründen auf "Well" (manchmal werden Proben geloopt, manchmal werden alle Wells in einer Platte geloopt). Ich konnte mir keine Fälle vorstellen, in denen Sample nicht auf den Brunnen zurückgreifen würde, in dem es platziert ist.
(Zeitpunkt 1): Ein Jahr später werden viele Anwendungsfälle implementiert .... und es gibt Fälle, in denen die Probe nicht mehr auf den Brunnen zurückgreifen sollte, in dem sie platziert ist. In einem Arbeitsschritt befinden sich temporäre Platten. Hier bezieht sich eine Vertiefung auf eine Probe, die sich wiederum auf eine Vertiefung auf einer anderen Platte bezieht. Aus diesem Grund tritt manchmal seltsames Verhalten auf, wenn jemand versucht, neue Funktionen zu implementieren. Es braucht Zeit, um einzudringen.
Mir hat auch dies geholfen Artikel , das oben über negative Aspekte des faulen Ladens erwähnt wurde.
Entstammt das einem Buch von Eric Evans Domain Driven Design oder stammt es von einer anderen Stelle?
Es ist altes Zeug. Eric`s Buch hat es nur ein bisschen lauter gemacht.
Wo gibt es einige gute Erklärungen für die Argumentation dahinter?
Die Vernunft ist einfach - der menschliche Geist wird schwach, wenn er mit vage verwandten multiplen Kontexten konfrontiert ist. Sie führen zu Mehrdeutigkeiten (Amerika in Süd-/Nordamerika bedeutet Süd-/Nordamerika), Mehrdeutigkeiten führen zu einer ständigen Zuordnung von Informationen, wenn der Verstand sie "berührt", was sich in schlechter Produktivität und Fehlern niederschlägt.
Die Geschäftslogik sollte so klar wie möglich wiedergegeben werden. Fremdschlüssel, Normalisierung und objektrelationale Zuordnung stammen aus einem völlig anderen Bereich - diese Dinge sind technisch und computerbezogen.
In Analogie: Wenn Sie lernen, wie man von Hand schreibt, sollten Sie nicht mit dem Verständnis belastet sein, wo der Stift hergestellt wurde, warum Tinte auf Papier haftet, wann Papier erfunden wurde und was andere berühmte chinesische Erfindungen sind.
bearbeiten: Zur Verdeutlichung: Ich spreche nicht von der klassischen OO Praxis, den Datenzugriff von der Geschäftslogik in eine separate Ebene zu trennen soll überhaupt mit der Datenzugriffsebene sprechen (dh sie sollen keine Verweise auf Repository-Objekte enthalten)
Der Grund ist immer noch derselbe, den ich oben erwähnt habe. Hier ist es nur ein Schritt weiter. Warum sollten Entitäten teilweise unwissend sein, wenn sie (zumindest in der Nähe von) vollständig sein können? Weniger domänenunabhängige Bedenken hat unser Modell - mehr Raum zum Atmen, den unser Geist bekommt, wenn er es neu interpretieren muss.