Entwurfsmuster für den Datenzugriffslayer

8

Ich habe eine Anwendung, die eine Datenbank (MongoDB) verwendet, um Informationen zu speichern. In der Vergangenheit habe ich eine Klasse voller statischer Methoden verwendet, um Daten zu speichern und abzurufen, aber ich habe festgestellt, dass dies nicht sehr objektorientiert oder zukunftssicher ist.

Auch wenn es sehr unwahrscheinlich ist, dass ich die Datenbank ändere, würde ich lieber etwas tun, das mich nicht zu stark an Mongo bindet. Ich möchte auch in der Lage sein, Ergebnisse mit der Option zu cachen, um das zwischengespeicherte Objekt von der Datenbank zu aktualisieren, aber das ist nicht wesentlich und kann an einem anderen Ort erfolgen.

Ich habe Datenzugriffsobjekte betrachtet, aber sie scheinen nicht sehr gut definiert zu sein und ich kann keine guten Beispiele für die Implementierung finden (in Java oder einer ähnlichen Sprache). Ich habe auch viele einmalige Fälle wie das Finden von Benutzernamen für die Tab-Vervollständigung, die nicht gut geeignet erscheinen und das DAO groß und aufgebläht machen würden.

Gibt es Entwurfsmuster, die das Abrufen und Speichern von Objekten erleichtern, ohne zu datenbankspezifisch zu sein? Gute Beispiele für die Implementierung wären hilfreich (vorzugsweise in Java).

    
user2248702 31.12.2014, 16:06
quelle

1 Antwort

33

Nun, der übliche Ansatz zur Datenspeicherung in Java ist, wie Sie anmerkten, überhaupt nicht sehr objektorientiert. Dies ist an und für sich weder schlecht noch gut: "Objektorientiertheit" ist weder ein Vorteil noch ein Nachteil, es ist nur eines von vielen Paradigmen, die manchmal mit gutem Architekturdesign helfen (und manchmal nicht).

Der Grund, warum DAOs in Java nicht in der Regel objektorientiert sind, ist genau das, was Sie erreichen wollen - um Ihre Abhängigkeit von der Datenbank zu lockern. In einer besser gestalteten Sprache, die eine mehrfache Vererbung zulässt, kann dies natürlich sehr elegant und objektorientiert erfolgen, aber mit Java scheint es mehr Ärger zu geben, als es wert ist.

Im weiteren Sinne hilft der Non-OO-Ansatz, Ihre Daten auf Anwendungsebene von der Art der Speicherung zu entkoppeln. Dies ist mehr als eine (nicht) Abhängigkeit von den Besonderheiten einer bestimmten Datenbank, aber auch von den Speicherschemata, was besonders wichtig ist, wenn relationale Datenbanken verwendet werden (lassen Sie mich nicht mit ORM beginnen): Sie können ein gut gestaltetes relationales Schema haben nahtlos in die Anwendung OO-Modell von Ihrem DAO übersetzt.

Was die meisten DAOs heutzutage in Java sind, sind im Wesentlichen das, was Sie am Anfang erwähnt haben - Klassen, voll statischer Methoden. Ein Unterschied besteht darin, dass anstatt alle Methoden statisch zu machen, es besser ist, eine einzige statische "Factory-Methode" (wahrscheinlich in einer anderen Klasse) zu verwenden, die eine (Singleton) -Instanz Ihres DAO zurückgibt, die eine bestimmte Schnittstelle implementiert Wird vom Anwendungscode für den Zugriff auf die Datenbank verwendet:

%Vor%

Warum geht das im Gegensatz zu statischen Methoden so? Nun, was ist, wenn Sie sich entscheiden, zu einer anderen Datenbank zu wechseln? Natürlich würden Sie eine neue DAO-Klasse erstellen und die Logik für Ihren neuen Speicher implementieren. Wenn Sie statische Methoden verwenden würden, müssten Sie nun Ihren gesamten Code durchlaufen und das DAO ändern, um Ihre neue Klasse zu verwenden, richtig? Dies könnte ein großer Schmerz sein. Und was ist, wenn Sie Ihre Meinung ändern und zur alten db zurückkehren möchten?

Bei diesem Ansatz müssen Sie lediglich GreatDAOFactory.getDAO() ändern und eine Instanz einer anderen Klasse erstellen. Der gesamte Anwendungscode wird die neue Datenbank ohne Änderungen verwenden.

Im realen Leben geschieht dies oft ohne irgendwelche Änderungen am Code: Die Factory-Methode ruft den Namen der Implementierungsklasse über eine Eigenschaftseinstellung ab und instanziiert sie mithilfe von Reflektion. Sie müssen also nur noch die Implementierungen wechseln Bearbeiten Sie eine Eigenschaftendatei. Es gibt tatsächlich Frameworks - wie spring oder guice -, die diesen "Dependency Injection" -Mechanismus für Sie verwalten, aber ich werde nicht auf Details eingehen, erstens, weil das wirklich außerhalb des Bereichs Ihrer Frage liegt, und weil ich nicht unbedingt davon überzeugt bin, dass der Nutzen, den Sie aus der Verwendung dieser Frameworks ziehen, die Mühe wert ist, sie für die meisten Anwendungen zu integrieren.

Ein anderer (wahrscheinlich wahrscheinlicherer) Nutzen dieses "Fabrikansatzes" im Gegensatz zu statisch ist die Testbarkeit. Stellen Sie sich vor, Sie schreiben einen Komponententest, der die Logik Ihrer App -Klasse unabhängig von einem zugrunde liegenden DAO testen soll. Sie möchten nicht, dass sie aus mehreren Gründen einen echten zugrunde liegenden Speicher verwendet (Geschwindigkeit, Einrichten und Nachbereinigung, mögliche Kollisionen mit anderen Tests, Möglichkeit der Verschmutzung von Testergebnissen mit Problemen in DAO, unabhängig von App , die gerade getestet wird, etc.).

Dafür möchten Sie ein Testframework wie Mockito , mit dem Sie die Funktionalität eines Objekts oder einer Methode "überspielen" können, indem Sie es durch ein "Dummy" -Objekt mit vordefiniertem Verhalten ersetzen (ich werde es tun) Überspringe die Details, denn das ist wiederum nicht möglich. Sie können also dieses Dummy-Objekt erstellen, das Ihr DAO ersetzt, und dafür sorgen, dass das GreatDAOFactory Ihren Dummy anstelle des echten Objekts zurückgibt, indem Sie GreatDAOFactory.setDAO(dao) vor dem Test aufrufen (und danach wiederherstellen). Wenn Sie statt der Instanzklasse statische Methoden verwenden würden, wäre dies nicht möglich.

Ein weiterer Vorteil, der dem oben beschriebenen Wechsel von Datenbanken ähnelt, ist das "Auffrischen" Ihres Daos mit zusätzlichen Funktionen. Angenommen, Ihre Anwendung wird langsamer, wenn die Datenmenge in der Datenbank zunimmt, und Sie entscheiden, dass Sie eine Cache-Ebene benötigen. Implementieren Sie eine Wrapper-Klasse, die die reale Dao-Instanz (die ihr als Konstruktorparameter zur Verfügung gestellt wird) für den Zugriff auf die Datenbank verwendet und die im Speicher gelesenen Objekte zwischenspeichert, damit sie schneller zurückgegeben werden können. Sie können dann Ihren GreatDAOFactory.getDAO diesen Wrapper instanziieren, damit die Anwendung davon profitiert.

(Dies wird als "delegation pattern" bezeichnet ...) und es scheint, als ob Sie Schmerzen in Ihrem Hintern haben, besonders wenn Sie viele Methoden in Ihrem DAO definiert haben: Sie müssen alle im Wrapper implementieren, sogar um das Verhalten zu ändern von nur einem. Alternativ könnten Sie einfach Ihre Dao Unterklasse, und fügen Sie Caching auf diese Weise.Dies wäre eine viel weniger langweilige Programmierung im Voraus, kann aber problematisch werden, wenn Sie sich entscheiden, die Datenbank zu ändern, oder, noch schlimmer, eine Option haben, Implementierungen hin und her zu schalten).

Eine ähnlich weit verbreitete (aber meiner Meinung nach minderwertige) Alternative zur "factory" -Methode ist, das dao in allen Klassen, die es brauchen, zu einer Mitgliedsvariablen zu machen:

%Vor%

Auf diese Weise muss der Code, der diese Klassen instanziiert, das DAO-Objekt instanziieren (könnte die Factory weiterhin verwenden) und als Konstruktorparameter bereitstellen. Die oben erwähnten Abhängigkeitsinjektions-Frameworks machen normalerweise etwas Ähnliches.

Dies bietet alle Vorteile des Ansatzes der "Fabrikmethode", den ich vorher beschrieben habe, aber, wie ich sagte, ist meiner Meinung nach nicht so gut. Die Nachteile bestehen darin, dass Sie für jede Ihrer App-Klassen einen Konstruktor schreiben müssen, der genau dasselbe vor und zurück macht und die Klassen bei Bedarf nicht einfach instanziieren kann, und einige Lesbarkeit verloren geht: mit einer ausreichend großen Codebasis Ein Leser Ihres Codes, der damit nicht vertraut ist, wird schwer verstehen, welche tatsächliche Implementierung des Daos verwendet wird, wie es instanziiert wird, ob es ein Singleton ist, eine threadsichere Implementierung, ob es den Zustand hält oder Caches alles, wie die Entscheidungen über die Auswahl einer bestimmten Implementierung getroffen werden usw.

    
Dima 31.12.2014, 17:37
quelle