Was sind die Nachteile des ActiveRecord-Musters?

30

Ich bin gespannt, welche Nachteile die Verwendung des ActiveRecord-Musters für Datenzugriffs- / Geschäftsobjekte hat. Das Einzige, woran ich auf Anhieb denken kann, ist, dass es gegen das Prinzip der einfachen Verantwortung verstößt, aber das AR-Muster ist weit genug verbreitet, dass dieser Grund alleine nicht "gut genug" zu sein scheint, um es zu rechtfertigen, es nicht zu verwenden (natürlich mein Die Ansicht kann verzerrt sein, da häufig keiner der Codes, mit denen ich arbeite, einem der SOLID-Prinzipien folgt .

Persönlich bin ich kein Fan von ActiveRecord (mit Ausnahme des Schreibens einer Ruby on Rails-Anwendung, bei der AR sich "natürlich" anfühlt), weil die Klasse zu viel tut und der Datenzugriff nicht der Klasse selbst überlassen bleiben sollte zu handhaben. Ich bevorzuge die Verwendung von Repositorys, die Geschäftsobjekte zurückgeben. Der Großteil des Codes, mit dem ich arbeite, verwendet in der Regel eine Variante von ActiveRecord in Form von (ich weiß nicht, warum die Methode ein Boolescher Wert ist):

public class Foo
{
    // properties...

    public Foo(int fooID)
    {
        this.fooID = fooID;
    }

    public bool Load()
    {
        // DB stuff here...
        // map DataReader to properties...

        bool returnCode = false;
        if (dr.HasRows)
            returnCode = true;

        return returnCode;
    }
}

oder manchmal die "traditionellere" Art, eine public static Foo FindFooByID(int fooID)Methode für die Finder und etwas in der Art des public void Save()Speicherns / Aktualisierens zu haben.

Ich verstehe, dass ActiveRecord in der Regel viel einfacher zu implementieren und zu verwenden ist, aber für komplexe Anwendungen etwas zu einfach erscheint und Sie eine robustere Architektur haben könnten, indem Sie Ihre Datenzugriffslogik in einem Repository kapseln (ganz zu schweigen davon, dass es einfacher ist, sie auszutauschen) Datenzugriffsstrategien (z. B. verwenden Sie Stored Procs + DataSets und möchten zu LINQ wechseln oder so)

Was sind weitere Nachteile dieses Musters, die bei der Entscheidung, ob ActiveRecord der beste Kandidat für den Job ist, berücksichtigt werden sollten?

Wayne Molina
quelle

Antworten:

28

Der Hauptnachteil ist, dass Ihre "Entitäten" sich ihrer eigenen Beständigkeit bewusst sind, was zu vielen anderen schlechten Entwurfsentscheidungen führt.

Das andere Problem ist, dass die meisten aktiven Datensatz-Toolkits Tabellenfeldern im Grunde genommen 1 zu 1 mit null Indirektionsebenen zuordnen. Dies funktioniert auf kleinen Skalen, fällt jedoch auseinander, wenn Sie schwierigere Probleme zu lösen haben.


Wenn Sie wissen, wie lange Ihre Objekte bestehen, müssen Sie Folgendes tun:

  • Datenbankverbindungen sind leicht überall verfügbar. Dies führt normalerweise zu unangenehmer Hardcodierung oder einer statischen Verbindung, die von überall her getroffen wird.
  • Ihre Objekte sehen eher wie SQL aus als wie Objekte.
  • Es ist schwierig, irgendetwas in der App zu tun.

Hinzu kommen eine ganze Reihe weiterer Fehlentscheidungen.

Wyatt Barnett
quelle
2
Können Sie auf "andere schlechte Designentscheidungen" eingehen?
Kevin Cline
2
Vielen Dank. Ich habe nicht festgestellt, dass diese Probleme bei der Entwicklung von Ruby on Rails ein Problem darstellen. Es ist weiterhin möglich, Verhalten und Ausdauer separat zu testen. IMO, die Beharrlichkeit vom Verhalten trennt, hat wenig praktischen Wert.
Kevin Cline
@ Kevin: Diese Dinge sind weniger ein Nachteil mit Ruby-Funktionen wie Mixins und Duck Typing. Bei statischen Sprachen - wie C #, wie es das OP in seiner Frage verwendete - ist es etwas schwieriger, die beiden zu trennen.
Wyatt Barnett
@Wayne: Für mich sind Klassen nur Kästchen zum Einfügen von Methoden - ich kann die Geschäftslogik von der Persistenz trennen, indem ich sie in separate Klassen einfüge, oder ich kann sie nur konzeptionell trennen, indem ich sicher gehe, dass die Geschäftsmethoden nicht funktionieren. O. In Sprachen mit geringer Delegierungsunterstützung (z. B. Java) wird dadurch eine große Menge an Code eingespart. OTOH, ich gehe gerade in den Ruhezustand, damit ich mich völlig irren kann.
Kevin Cline
4
Ich würde zwei Dinge hinzufügen; 1. Die Kopplung mit dem Persistenzmechanismus macht es schwierig, wenn nicht unmöglich, den Code richtig zu testen. 2. Active Record klebt Ihren Code in einem zentralisierten Persistenzmechanismus an die Beziehungen, wodurch es wirklich schwierig wird, Ihren Monolithen zu teilen, wenn Sie sich jemals dazu entschließen.
Istepaniuk
15

Der größte Nachteil aktiver Datensätze besteht darin, dass Ihre Domain normalerweise eng an einen bestimmten Persistenzmechanismus gekoppelt ist. Sollte dieser Mechanismus eine globale Änderung erfordern, möglicherweise von dateibasierter zu DB-basierter Persistenz, oder zwischen Datenzugriffs-Frameworks, kann sich JEDE Klasse, die dieses Muster implementiert, ändern. Abhängig von der Sprache, dem Framework und dem Design kann es sogar erforderlich sein, dass jedes Objekt durchlaufen wird, um die Datenzugriffsmethoden zu aktualisieren. Dies ist in den meisten Sprachen, die einen einfachen Zugriff ermöglichen, selten Dateien mit Verbindungszeichenfolgen konfigurieren).

Im Allgemeinen müssen Sie sich auch wiederholen. Die meisten Persistenzmechanismen haben viele gemeinsame Codes, um eine Verbindung zu einer Datenbank herzustellen und eine Transaktion zu starten. DRY (Don't Repeat Yourself) würde Ihnen als Codierer empfehlen, eine solche Logik zu zentralisieren.

Es macht auch atomare Operationen schwierig. Wenn eine Gruppe von Objekten vollständig oder vollständig gespeichert werden muss (z. B. eine Rechnung und ihre Rechnungsposten und / oder Kunden- und / oder FIBU-Einträge), muss eines der Objekte alle diese anderen Objekte kennen und deren Beständigkeit steuern ( Dies dehnt den Bereich des steuernden Objekts aus. Große miteinander verbundene Datensätze können leicht zu "Gottobjekten" werden, die alles über ihre Abhängigkeiten wissen. Oder die Kontrolle über die gesamte Transaktion muss von außerhalb der Domäne erfolgen (und in diesem Fall verwenden Sie warum) AR?)

Es ist auch "falsch" aus einer objektorientierten Perspektive. In der Praxis weiß eine Rechnung nicht, wie sie sich selbst ablegen soll. Warum kann ein Rechnungscodeobjekt sich selbst in der Datenbank speichern? Natürlich würde eine zu religiöse Bindung an "Objekte sollten nur das modellieren, was ihre realen Gegenstücke können" zu anämischen Domänenmodellen führen (eine Rechnung weiß auch nicht, wie sie ihre eigene Gesamtsumme berechnet, teilt aber die Berechnung dieser Gesamtsumme auf Ein anderes Objekt wird im Allgemeinen als schlechte Idee angesehen.

KeithS
quelle
In Ruby, wo die Persistenz hinter einem sehr einfachen Schlüsselwort oder Befehl definiert oder abstrahiert werden kann, ist dies wahrscheinlich weniger problematisch. In .NET, für das mehr LoC erforderlich ist, um verschiedene Persistenzmechanismen einzurichten, ist es normalerweise überflüssig, für jedes Objekt eine SQL-Verbindung zu haben, insbesondere wenn mehrere Objekte in einer atomaren Transaktion gespeichert werden müssen. Das Herstellen einer Verbindung zur Datenbank sieht genauso aus, egal ob Sie eine Rechnung oder einen Kunden speichern. Sie würden entweder das allgemeine Material in eine Basisklasse abstrahieren, die allen ActiveRecord-Objekten in Ihrer Codebasis gemeinsam ist, oder es in eine eigene Klasse (ein Repository)
extrahieren
Können Sie Beispiele für die Verwendung von Ruby ActiveRecord bereitstellen, die Ihre Punkte veranschaulichen? Ich hatte diese Probleme nicht, aber meine Bewerbung war ziemlich klein. Ich könnte Migrationen in Ruby schreiben und sie auf verschiedenen DB-Implementierungen bereitstellen. Ich fand, dass Rubys ActiveRecord sehr nützlich war, um Wiederholungen zu vermeiden, und verstehe daher nicht, warum Sie behaupten würden, dass die Verwendung von ActiveRecord den gegenteiligen Effekt hätte. Ich hatte keine Probleme beim Speichern: Ich habe gerade das Objektmodell geändert und AR die Datenbank aktualisieren lassen, um das Objektmodell widerzuspiegeln.
Kevin Cline
2

Der grundlegende Nachteil ist, dass Ihr Domain-Modell dadurch komplex wird, weil es nicht nur Geschäftslogik, sondern auch Persistenzinformationen enthält.

Die Lösung besteht also darin, die Data Mapper- Implementierung von ORM zu verwenden. Das trennt die Persistenzschicht und wir konzentrieren uns jetzt mehr auf die Geschäftslogik von Einheiten. Lehre ist Data Mapper ORM.

Aber dieser Ansatz hat auch einige Komplexität. Für Abfragen ist man jetzt zu sehr auf Data Mapper angewiesen, macht eine abfrageorientierte Umgebung. Zur Vereinfachung wird eine weitere Ebene zwischen Domain Model und Data Mapper eingeführt, die als Repository bezeichnet wird .

Das Repository abstrahiert die Persistenzschicht. Die objektorientierte Programmierung fühlt sich in dem Sinne an, dass es sich um eine Sammlung aller Objekte des gleichen Typs handelt (wie alle in der Datenbanktabelle gespeicherten Entitäten), und Sie können sie als Sammeloperation ausführen, hinzufügen , entfernen . enthält usw.

Für Benutzerentität gibt es beispielsweise UserRepository , das eine Auflistung derselben Art von Benutzerobjekten (die in der Benutzertabelle gespeichert sind) darstellt, für die Sie Vorgänge ausführen können. Zur Abfrage der Benutzertabelle wird der Benutzerdaten-Mapper verwendet, der jedoch an das Domänenmodell Benutzer abstrahiert wird .

Repository - Muster Typ ist Data Access Layer , ist ein weiteres Data Access Object nur Unterschiede Repository hat Aggregate Root - Feature

palash140
quelle
Repository- und DAO- Muster unterscheiden sich, DAO ist der allgemeine Datenzugriff, Repository dient zum dauerhaften Sammeln aller Objekte des gleichen Typs. Dh alle Repositories sollten die gleiche Schnittstelle haben (wie Array oder Liste). Andere Methoden gehören nicht zum Repository. DAO ist eine niedrigere Ebene, das Repository verwendet möglicherweise DAO. Oft verwenden Programmierer (insbesondere PHP) Repository als DAO, aber es ist nicht korrekt.
Xmedeko