Während ich mehr und mehr über OOP lerne und verschiedene Entwurfsmuster implementiere, komme ich immer wieder auf Fälle zurück, in denen Menschen Active Record hassen .
Oft sagen die Leute, dass es nicht gut skaliert (unter Berufung auf Twitter als Paradebeispiel) - aber niemand erklärt tatsächlich, warum es nicht gut skaliert. und / oder wie man die Vorteile von AR ohne die Nachteile erreicht (über ein ähnliches, aber anderes Muster?)
Hoffentlich wird dies nicht zu einem heiligen Krieg um Designmuster - alles, was ich wissen möchte, ist **** speziell ****, was mit Active Record nicht stimmt.
Wenn es nicht gut skaliert, warum nicht?
Welche anderen Probleme hat es?
ruby-on-rails
design-patterns
oop
activerecord
Adam Tuttle
quelle
quelle
Antworten:
Es gibt ActiveRecord, das Entwurfsmuster, und ActiveRecord, die Rails-ORM-Bibliothek , und es gibt auch eine Menge Nachahmungen für .NET und andere Sprachen.
Das sind alles verschiedene Dinge. Sie folgen größtenteils diesem Entwurfsmuster, erweitern und modifizieren es jedoch auf viele verschiedene Arten. Bevor also jemand "ActiveRecord Sucks" sagt, muss es qualifiziert werden, indem er sagt: "Welcher ActiveRecord, es gibt Haufen?"
Ich bin nur mit Rails ActiveRecord vertraut. Ich werde versuchen, alle Beschwerden zu bearbeiten, die im Zusammenhang mit der Verwendung von Rails vorgebracht wurden.
Code:
Dadurch wird SQL mit
LEFT JOIN companies on companies.id = person.company_id
und automatisch zugehörige Unternehmensobjekte generiert, sodass Sie dies tun könnenpeople.first.company
und die Datenbank nicht aufgerufen werden muss, da die Daten bereits vorhanden sind.Code:
Dies wird nicht empfohlen, da es hässlich ist. In den Fällen, in denen Sie einfach nur SQL schreiben müssen, ist dies jedoch problemlos möglich.
Code:
Dadurch werden nur die Namens- und ID-Spalten aus der Datenbank ausgewählt. Alle anderen 'Attribute' in den zugeordneten Objekten sind nur Null, es sei denn, Sie laden das Objekt manuell neu und so weiter.
quelle
Ich habe immer festgestellt, dass ActiveRecord für schnelle CRUD-basierte Anwendungen geeignet ist, bei denen das Modell relativ flach ist (wie in nicht vielen Klassenhierarchien). Für Anwendungen mit komplexen OO-Hierarchien ist ein DataMapper jedoch wahrscheinlich die bessere Lösung. Während ActiveRecord ein 1: 1-Verhältnis zwischen Ihren Tabellen und Ihren Datenobjekten annimmt, wird diese Art von Beziehung bei komplexeren Domänen unhandlich. In seinem Buch über Muster weist Martin Fowler darauf hin, dass ActiveRecord unter Bedingungen, unter denen Ihr Modell ziemlich komplex ist, zum Zusammenbruch neigt, und schlägt als Alternative einen DataMapper vor .
Ich habe festgestellt, dass dies in der Praxis zutrifft. In Fällen, in denen Ihre Domäne häufig vererbt wird, ist es schwieriger, die Vererbung Ihrem RDBMS zuzuordnen, als Assoziationen oder Zusammensetzungen zuzuordnen.
Ich habe "Domain" -Objekte, auf die Ihre Controller über diese DataMapper-Klassen (oder "Service Layer") zugreifen. Diese spiegeln die Datenbank nicht direkt wider, sondern dienen als OO-Darstellung für ein reales Objekt. Angenommen, Sie haben eine Benutzerklasse in Ihrer Domäne und müssen Verweise auf oder Sammlungen anderer Objekte haben, die bereits geladen wurden, wenn Sie dieses Benutzerobjekt abrufen. Die Daten stammen möglicherweise aus vielen verschiedenen Tabellen, und ein ActiveRecord-Muster kann es sehr schwierig machen.
Anstatt das Benutzerobjekt direkt zu laden und über eine ActiveRecord-API auf Daten zuzugreifen, ruft Ihr Controller-Code ein Benutzerobjekt ab, indem Sie beispielsweise die API der UserMapper.getUser () -Methode aufrufen. Es ist dieser Mapper, der dafür verantwortlich ist, alle zugeordneten Objekte aus ihren jeweiligen Tabellen zu laden und das fertige Benutzerdomänenobjekt an den Aufrufer zurückzugeben.
Im Wesentlichen fügen Sie nur eine weitere Abstraktionsebene hinzu, um den Code besser verwalten zu können. Ob Ihre DataMapper-Klassen benutzerdefiniertes SQL enthalten oder eine API für die Datenabstraktionsschicht aufrufen oder sogar selbst auf ein ActiveRecord-Muster zugreifen, spielt für den Controller-Code, der ein nettes, ausgefülltes Benutzerobjekt empfängt, keine Rolle.
Jedenfalls mache ich das so.
quelle
Ich denke, es gibt wahrscheinlich ganz andere Gründe, warum Leute ActiveRecord "hassen" und was daran "falsch" ist.
In Bezug auf das Thema Hass gibt es viel Gift für alles, was mit Rails zu tun hat. Was daran falsch ist, ist es wahrscheinlich, dass es wie jede Technologie ist und es Situationen gibt, in denen es eine gute Wahl ist, und Situationen, in denen es bessere Entscheidungen gibt. Nach meiner Erfahrung ist die Datenbank schlecht strukturiert, wenn Sie die meisten Funktionen von Rails ActiveRecord nicht nutzen können. Wenn Sie auf Daten ohne Primärschlüssel zugreifen und Dinge, die gegen die erste normale Form verstoßen und für die viele gespeicherte Prozeduren erforderlich sind, um auf die Daten zuzugreifen, ist es besser, etwas zu verwenden, das eher nur ein SQL-Wrapper ist. Wenn Ihre Datenbank relativ gut strukturiert ist, können Sie dies mit ActiveRecord nutzen.
Hinzufügen zum Thema Antworten auf Kommentatoren, die sagen, dass es in ActiveRecord schwierig ist, mit einer Gegenerwiderung aus einem Code-Snippet zu antworten
Mit der Option include können Sie mit ActiveRecord das Standardverhalten beim verzögerten Laden überschreiben.
quelle
Meine lange und späte Antwort, nicht einmal vollständig, aber eine gute Erklärung, WARUM ich dieses Muster, diese Meinungen und sogar einige Emotionen hasse:
1) Kurzversion: Active Record erstellt eine " dünne Schicht " " starker Bindung " zwischen der Datenbank und dem Anwendungscode. Was keine logischen, keine Probleme löst, überhaupt keine Probleme. IMHO liefert es keinen Wert, außer etwas syntaktischem Zucker für den Programmierer (der dann eine "Objektsyntax" verwenden kann, um auf einige Daten zuzugreifen, die in einer relationalen Datenbank vorhanden sind). Die Bemühungen, den Programmierern einen gewissen Komfort zu bieten, sollten (IMHO ...) besser in Tools für den Datenbankzugriff auf niedriger Ebene investiert werden, z. B. einige Variationen einfacher, einfacher, einfacher
hash_map get_record( string id_value, string table_name, string id_column_name="id" )
und ähnlicher Methoden (natürlich variieren die Konzepte und die Eleganz stark mit den verwendete Sprache).2) lange Version: In allen datenbankgesteuerten Projekten, in denen ich die "konzeptionelle Kontrolle" über die Dinge hatte, habe ich AR vermieden und es war gut. Normalerweise erstelle ich eine mehrschichtige Architektur (Sie teilen Ihre Software früher oder später in Schichten auf, zumindest in mittelgroßen bis großen Projekten):
A1) die Datenbank selbst, Tabellen, Beziehungen, sogar eine Logik, wenn das DBMS dies zulässt (MySQL ist jetzt auch erwachsen)
A2) Sehr oft gibt es mehr als einen Datenspeicher: Dateisystem (Blobs in der Datenbank sind nicht immer eine gute Entscheidung ...), Legacy-Systeme (stellen Sie sich vor, "wie" auf sie zugegriffen wird, viele Varianten möglich ... aber das ist es nicht der Punkt...)
B) Datenbankzugriffsschicht (auf dieser Ebene sind Werkzeugmethoden und Helfer für den einfachen Zugriff auf die Daten in der Datenbank sehr willkommen, aber AR bietet hier keinen Wert, außer etwas syntaktischem Zucker).
C) Anwendungsobjektschicht: "Anwendungsobjekte" sind manchmal einfache Zeilen einer Tabelle in der Datenbank, aber meistens handelt es sich ohnehin um zusammengesetzte Objekte, an die eine höhere Logik angehängt ist. Daher ist es einfach nutzlos, Zeit in AR-Objekte auf dieser Ebene zu investieren , eine Verschwendung wertvoller Codiererzeit, denn der "reale Wert", die "höhere Logik" dieser Objekte muss sowieso über den AR-Objekten implementiert werden - mit und ohne AR! Und warum möchten Sie beispielsweise eine Abstraktion von "Protokolleintragsobjekten" haben? App-Logikcode schreibt sie, aber sollte das die Möglichkeit haben, sie zu aktualisieren oder zu löschen? klingt albern und
App::Log("I am a log message")
ist einige Größen einfacher zu bedienen alsle=new LogEntry(); le.time=now(); le.text="I am a log message"; le.Insert();
. Und zum Beispiel: Die Verwendung eines "Protokolleintragsobjekts" in der Protokollansicht in Ihrer Anwendung funktioniert für 100, 1000 oder sogar 10000 Protokollzeilen, aber früher oder später müssen Sie optimieren - und ich wette, in den meisten Fällen werden Sie es einfach tun Verwenden Sie diese kleine schöne SQL SELECT-Anweisung in Ihrer App-Logik (die die AR-Idee völlig zerstört ..), anstatt diese kleine Anweisung in starre feste AR-Ideenrahmen mit viel Code zu verpacken und zu verbergen. Die Zeit, die Sie mit dem Schreiben und / oder Erstellen von AR-Code verschwendet haben, hätte in eine viel cleverere Oberfläche zum Lesen von Listen mit Protokolleinträgen investiert werden können (auf viele, viele Arten ist der Himmel die Grenze). Codierer sollten es wagen, neue Abstraktionen zu erfinden , um ihre Anwendungslogik zu realisieren, die zur beabsichtigten Anwendung passt, und dumme Muster nicht dumm neu implementieren, das klingt auf den ersten Blick gut!D) die Anwendungslogik - implementiert die Logik der Interaktion von Objekten und des Erstellens, Löschens und Auflistens (!) Von Anwendungslogikobjekten (NEIN, diese Aufgaben sollten selten in den Anwendungslogikobjekten selbst verankert sein: sagt das Blatt Papier auf Ihrem Schreibtisch Sie die Namen und Positionen aller anderen Blätter in Ihrem Büro? Vergessen Sie "statische" Methoden zum Auflisten von Objekten, das ist albern, ein schlechter Kompromiss, der geschaffen wurde, um die menschliche Denkweise in [einige-nicht-alle-AR-Framework-ähnliche] zu integrieren -] AR Denken)
E) Die Benutzeroberfläche - nun, was ich in den folgenden Zeilen schreiben werde, ist sehr, sehr, sehr subjektiv, aber meiner Erfahrung nach haben Projekte, die auf AR basieren, häufig den UI-Teil einer Anwendung vernachlässigt - Zeit wurde für die Erstellung obskurer Abstraktionen verschwendet . Am Ende haben solche Anwendungen viel Zeit für Codierer verschwendet und fühlen sich an wie Anwendungen von Codierern für Codierer, die innen und außen technisch orientiert sind. Die Programmierer fühlen sich gut an (harte Arbeit endlich erledigt, alles fertig und korrekt, gemäß dem Konzept auf Papier ...), und die Kunden "müssen nur lernen, dass es so sein muss", denn das ist "professionell". ok, sorry, ich schweife ab ;-)
Zugegeben, das alles ist subjektiv, aber es ist meine Erfahrung (Ruby on Rails ausgeschlossen, es kann anders sein, und ich habe keine praktische Erfahrung mit diesem Ansatz).
In bezahlten Projekten hörte ich oft die Forderung, zunächst einige "aktive Datensatz" -Objekte als Baustein für die übergeordnete Anwendungslogik zu erstellen. Nach meiner Erfahrung ist dies auffällig oftwar eine Art Entschuldigung dafür, dass der Kunde (in den meisten Fällen ein Softwareentwickler) kein gutes Konzept, keine große Sicht und keinen Überblick darüber hatte, was das Produkt letztendlich sein sollte. Diese Kunden denken in starren Rahmen ("in dem Projekt vor zehn Jahren hat es gut funktioniert ..."), sie können Entitäten ausarbeiten, sie können Entitätsbeziehungen definieren, sie können Datenbeziehungen auflösen und grundlegende Anwendungslogik definieren, aber dann hören sie auf und gib es dir und denke, das ist alles, was du brauchst ... ihnen fehlt oft ein vollständiges Konzept von Anwendungslogik, Benutzeroberfläche, Benutzerfreundlichkeit und so weiter ... ihnen fehlt die große Sicht und ihnen fehlt die Liebe für die Details, und sie möchten, dass Sie dieser AR-Art der Dinge folgen, denn ... nun, warum hat es in diesem Projekt vor Jahren funktioniert und die Leute beschäftigt und still gehalten? Ich weiß es nicht. Aber die "Details" trenne die Männer von den Jungen oder ... wie war der ursprüngliche Werbeslogan? ;-);
Nach vielen Jahren (zehn Jahre aktive Entwicklungserfahrung) läutet meine Alarmglocke, wenn ein Kunde ein "aktives Aufzeichnungsmuster" erwähnt. Ich habe gelernt, zu versuchen, sie in diese wesentliche Konzeptionsphase zurückzubringen , sie zweimal überlegen zu lassen, zu versuchen, ihre konzeptionellen Schwächen aufzuzeigen oder sie überhaupt zu vermeiden, wenn sie nicht erkennbar sind (am Ende wissen Sie, ein Kunde, der dies noch nicht tut weiß, was es will, denkt vielleicht sogar, dass es weiß, aber nicht, oder versucht, Konzeptarbeit kostenlos an MICH zu verlagern, kostet mich viele wertvolle Stunden, Tage, Wochen und Monate meiner Zeit, das Leben ist zu kurz ...).
Also endlich: DIESES ALLES ist der Grund, warum ich dieses alberne "aktive Aufnahmemuster" hasse, und ich tue es und werde es vermeiden, wann immer es möglich ist.
EDIT : Ich würde dies sogar als No-Pattern bezeichnen. Es löst kein Problem (Muster sollen keinen syntaktischen Zucker erzeugen). Es schafft viele Probleme: Die Wurzel all seiner Probleme (in vielen Antworten hier erwähnt ..) ist, dass es nur das gute alte, gut entwickelte und leistungsfähige SQL hinter einer Schnittstelle verbirgt, die nach der Musterdefinition extrem begrenzt ist.
Dieses Muster ersetzt Flexibilität durch syntaktischen Zucker!
Denken Sie darüber nach, welches Problem löst AR für Sie?
quelle
before_save
Rückrufe, um die Konsistenz innerhalb des Datensatzes aufrechtzuerhalten. 4)after_commit
Hooks für externe Service-Trigger. 5) Ein gutes DSL zum Organisieren von DDL-Änderungen in Änderungssätzen (Migrationen). (Es gibt immer noch Schmerzen, aber kein Muster zu haben ist schlimmer, wenn> 1 Entwickler.)Einige Nachrichten verwirren mich. Einige Antworten gehen zu "ORM" gegen "SQL" oder so ähnlich.
Tatsache ist, dass AR nur ein Programmiermuster zur Vereinfachung ist, bei dem Sie Ihre Domänenobjekte nutzen, um dort Datenbankzugriffscode zu schreiben.
Diese Objekte haben normalerweise Geschäftsattribute (Eigenschaften der Bean) und ein gewisses Verhalten (Methoden, die normalerweise mit diesen Eigenschaften arbeiten).
Der AR sagt nur "einige Methoden zu diesen Domänenobjekten hinzufügen" zu datenbankbezogenen Aufgaben.
Und ich muss meiner Meinung und Erfahrung nach sagen, dass mir das Muster nicht gefällt.
Auf den ersten Blick kann es ziemlich gut klingen. Einige moderne Java-Tools wie Spring Roo verwenden dieses Muster.
Für mich liegt das eigentliche Problem nur in der OOP-Sorge. Das AR-Muster zwingt Sie in gewisser Weise, eine Abhängigkeit von Ihrem Objekt zu Infrastrukturobjekten hinzuzufügen. Mit diesen Infrastrukturobjekten kann das Domänenobjekt die Datenbank mit den von AR vorgeschlagenen Methoden abfragen.
Ich habe immer gesagt, dass zwei Ebenen der Schlüssel zum Erfolg eines Projekts sind. Die Serviceschicht (in der sich die Geschäftslogik befindet oder über eine Remoting-Technologie exportiert werden kann, z. B. Web Services) und die Domänenschicht. Wenn wir den Domänenschichtobjekten zum Auflösen des AR-Musters einige Abhängigkeiten hinzufügen (die nicht wirklich benötigt werden), ist es meiner Meinung nach schwieriger, unsere Domänenobjekte mit anderen Schichten oder (seltenen) externen Anwendungen zu teilen.
Die Spring Roo-Implementierung von AR ist interessant, da sie nicht auf dem Objekt selbst beruht, sondern in einigen AspectJ-Dateien. Wenn Sie später jedoch nicht mit Roo arbeiten möchten und das Projekt umgestalten müssen, werden die AR-Methoden direkt in Ihren Domänenobjekten implementiert.
Eine andere Sicht. Stellen Sie sich vor, wir verwenden keine relationale Datenbank zum Speichern unserer Objekte. Stellen Sie sich vor, die Anwendung speichert unsere Domänenobjekte in einer NoSQL-Datenbank oder beispielsweise nur in XML-Dateien. Würden wir die Methoden, die diese Aufgaben ausführen, in unseren Domänenobjekten implementieren? Ich denke nicht (zum Beispiel würden wir im Fall von XM XML-bezogene Abhängigkeiten zu unseren Domänenobjekten hinzufügen ... Wirklich traurig, denke ich). Warum müssen wir dann die relationalen DB-Methoden in den Domänenobjekten implementieren, wie das Ar-Muster sagt?
Zusammenfassend kann das AR-Muster für kleine und einfache Anwendungen einfacher und gut klingen. Wenn wir jedoch komplexe und große Apps haben, denke ich, dass die klassische Schichtarchitektur ein besserer Ansatz ist.
quelle
Die ursprüngliche Frage ist mit Rails versehen und bezieht sich auf Twitter, das in Ruby on Rails erstellt wurde. Das ActiveRecord-Framework in Rails ist eine Implementierung des Active Record-Entwurfsmusters von Fowler.
quelle
Die Hauptsache, die ich in Bezug auf Beschwerden über Active Record gesehen habe, ist, dass Sie beim Erstellen eines Modells um eine Tabelle und beim Auswählen mehrerer Instanzen des Modells im Grunde genommen "select * from ..." ausführen. Dies ist in Ordnung, um einen Datensatz zu bearbeiten oder einen Datensatz anzuzeigen. Wenn Sie jedoch beispielsweise eine Liste der Städte für alle Kontakte in Ihrer Datenbank anzeigen möchten, können Sie "Stadt aus ... auswählen" und nur die Städte abrufen . Um dies mit Active Record zu tun, müssen Sie alle Spalten auswählen, jedoch nur City.
Unterschiedliche Implementierungen werden dies natürlich unterschiedlich behandeln. Trotzdem ist es ein Problem.
Jetzt können Sie dies umgehen, indem Sie ein neues Modell für die spezifische Aufgabe erstellen, die Sie ausführen möchten. Einige Leute würden jedoch argumentieren, dass dies mehr Aufwand als Nutzen bedeutet.
Ich grabe Active Record. :-)
HTH
quelle
Ich mag die Art und Weise, wie SubSonic nur eine Spalte macht.
Entweder
, oder:
Aber Linq ist immer noch König, wenn es um faules Laden geht.
quelle
@BlaM: Manchmal habe ich gerade einen aktiven Datensatz für ein Ergebnis eines Joins implementiert. Muss nicht immer die Beziehungstabelle <-> Active Record sein. Warum nicht "Ergebnis einer Join-Anweisung" <-> Active Record?
quelle
Ich werde über Active Record als Entwurfsmuster sprechen, ich habe ROR nicht gesehen.
Einige Entwickler hassen Active Record, weil sie intelligente Bücher über das Schreiben von sauberem und ordentlichem Code lesen. In diesen Büchern heißt es, dass Active Record gegen das Prinzip der einzelnen Resposobilität verstößt, gegen die DDD-Regel verstößt, dass Domänenobjekte dauerhaft ignorant sein sollten, und gegen viele andere Regeln aus dieser Art von Büchern .
Das zweite, was Domänenobjekte in Active Record in der Regel 1: 1 mit der Datenbank sind, kann in einigen Systemen als Einschränkung angesehen werden (meistens n-Tier).
Das sind nur abstrakte Dinge, ich habe Ruby on Rails nicht gesehen, wie dieses Muster tatsächlich umgesetzt wurde.
quelle
Das Problem, das ich bei Active Records sehe, ist, dass es immer nur um eine Tabelle geht. Das ist in Ordnung, solange Sie wirklich nur mit dieser einen Tabelle arbeiten, aber wenn Sie in den meisten Fällen mit Daten arbeiten, haben Sie irgendwo eine Art Join.
Ja, Join ist normalerweise schlechter als gar kein Join, wenn es um die Leistung geht, aber Join ist normalerweise besser als "gefälschter" Join, indem zuerst die gesamte Tabelle A gelesen und dann die gewonnenen Informationen zum Lesen und Filtern von Tabelle B verwendet werden.
quelle
Das Problem mit ActiveRecord besteht darin, dass die Abfragen, die automatisch für Sie generiert werden, Leistungsprobleme verursachen können.
Am Ende machen Sie einige unintuitive Tricks, um die Abfragen zu optimieren, bei denen Sie sich fragen, ob es zeitsparender gewesen wäre, die Abfrage überhaupt von Hand zu schreiben.
quelle
Obwohl alle anderen Kommentare zur SQL-Optimierung sicherlich gültig sind, ist meine Hauptbeschwerde mit dem aktiven Aufzeichnungsmuster, dass es normalerweise zu einer Impedanzfehlanpassung führt . Ich mag es, meine Domain sauber und richtig gekapselt zu halten, was das aktive Aufzeichnungsmuster normalerweise jede Hoffnung auf etwas zerstört.
quelle
Versuchen Sie eine viel zu viele polymorphe Beziehung. Nicht so einfach. Besonders wenn Sie STI nicht verwenden.
quelle