Ich schreibe eine Java-Webanwendung, die hauptsächlich aus einer Reihe ähnlicher Seiten besteht, in denen jede Seite mehrere Tabellen und einen Filter enthält, der für diese Tabellen gilt. Die Daten in diesen Tabellen stammen aus einer SQL-Datenbank.
Ich verwende myBatis als ORM, was in meinem Fall möglicherweise nicht die beste Wahl ist, da die Datenbank schlecht gestaltet ist und mybatis ein stärker datenbankorientiertes Tool ist.
Ich stelle fest, dass ich viel doppelten Code schreibe, da ich aufgrund des schlechten Designs der Datenbank unterschiedliche Abfragen für ähnliche Dinge schreiben muss, da diese Abfragen sehr unterschiedlich sein können. Das heißt, ich kann die Abfragen nicht einfach parametrisieren. Dies pflanzt sich in meinen Code fort und anstatt Zeilen in Spalten in meiner Tabelle mit einer einfachen Schleife zu füllen, habe ich folgenden Code:
Hole A Daten (p1, ..., pi);
erhalten B Daten (p1, ..., pi);
erhalten C - Daten (p1, ..., pi);
erhalten D - Daten (p1, ..., pi); ...
Und das explodiert bald, wenn wir unterschiedliche Tabellen mit unterschiedlichen Spalten haben.
Es trägt auch zur Komplexität bei, dass ich "Wicket" verwende, also eine Zuordnung von Objekten zu HTML-Elementen auf der Seite. So wird mein Java-Code zu einem Adapter zwischen der Datenbank und dem Front-End, wodurch ich viel Verkabelung, Boilerplate-Code mit einer darin eingemischten Logik erstelle.
Wäre es die richtige Lösung, die ORM-Mapper mit einer Extralayer zu umhüllen, die eine homogenere Schnittstelle zur Datenbank bietet, oder gibt es eine bessere Möglichkeit, mit diesem Spaghetti-Code umzugehen, den ich schreibe?
EDIT: Weitere Informationen zur Datenbank
Die Datenbank enthält hauptsächlich Informationen zu Telefonanrufen. Das schlechte Design besteht aus:
Tabellen mit einer künstlichen ID als Primärschlüssel, die nichts mit dem Domänenwissen zu tun haben.
Keine Eindeutigkeit, Trigger, Schecks oder Fremdschlüssel.
Felder mit einem generischen Namen, die unterschiedlichen Konzepten für unterschiedliche Datensätze entsprechen.
Datensätze, die nur durch Kreuzung mit anderen Tabellen mit anderen Bedingungen kategorisiert werden können.
Spalten, bei denen es sich um Zahlen oder Datumsangaben handeln soll, die als Zeichenfolgen gespeichert werden.
Um es zusammenzufassen, ein chaotisches / faules Design rundum.
Antworten:
Objektorientierung ist besonders deshalb von großem Wert, weil solche Szenarien auftreten und Sie Werkzeuge zum vernünftigen Entwerfen von Abstraktionen erhalten, mit denen Sie die Komplexität zusammenfassen können.
Die eigentliche Frage ist hier, wo Sie diese Komplexität zusammenfassen.
Lassen Sie mich einen Moment zurücktreten und über die Komplexität sprechen, auf die ich mich hier beziehe. Ihr Problem (so wie ich es verstehe; korrigieren Sie mich, wenn ich falsch liege) ist ein Persistenzmodell, das für die Aufgaben, die Sie mit den Daten ausführen müssen, nicht effektiv verwendbar ist. Es kann für andere Aufgaben effektiv und verwendbar sein, aber nicht für Ihre Aufgaben.
Was machen wir also, wenn wir Daten haben, die kein gutes Modell für unsere Mittel darstellen?
Übersetzen. Du bist das Einzige tun kannst . Diese Übersetzung ist die 'Komplexität', auf die ich mich oben beziehe. Da wir nun akzeptieren, dass wir das Modell übersetzen, müssen wir uns für einige Faktoren entscheiden.
Müssen wir beide Richtungen übersetzen? Werden beide Richtungen gleich übersetzt, wie in:
(Tbl A, Tbl B) -> Obj X (lesen)
Obj X -> (Tbl A, Tbl B) (schreiben)
oder stellen Einfüge- / Aktualisierungs- / Löschaktivitäten einen anderen Objekttyp dar, sodass Sie Daten als Obj X lesen, aber Daten aus Obj Y eingefügt / aktualisiert werden? Welche dieser beiden Möglichkeiten Sie nutzen möchten oder ob keine Aktualisierung / Einfügung / Löschung möglich ist, ist ein wichtiger Faktor für den Ort, an dem Sie die Übersetzung einfügen möchten.
Wo übersetzen Sie?
Zurück zu der ersten Aussage, die ich in dieser Antwort gemacht habe; OO ermöglicht es Ihnen , zu verkapseln Komplexität und was ich hier beziehen , ist die Tatsache , dass nicht nur sollten Sie, aber Sie müssen diese Komplexität kapseln , wenn Sie möchten, um sicherzustellen , nicht durchsickern und sickern in den gesamten Code. Gleichzeitig ist es wichtig zu erkennen, dass Sie keine haben können perfekte Abstraktion haben können. Sorgen Sie sich also weniger darum als darum, eine sehr effektive und verwendbare zu haben.
Wieder jetzt; Ihr Problem ist: Wo setzen Sie diese Komplexität ein? Nun, Sie haben die Wahl.
Sie können dies mit gespeicherten Prozeduren in der Datenbank tun . Dies hat den Nachteil, dass es mit ORMs oft nicht sehr gut funktioniert, aber das stimmt nicht immer. Gespeicherte Prozeduren bieten einige Vorteile, darunter häufig die Leistung. Gespeicherte Prozeduren können jedoch eine Menge Wartung erfordern, aber es liegt an Ihnen, Ihr spezielles Szenario zu analysieren und zu sagen, ob die Wartung mehr oder weniger als andere Entscheidungen sein wird. Ich persönlich kenne mich mit gespeicherten Prozeduren sehr gut aus, und als solche reduziert diese Tatsache des verfügbaren Talents den Overhead. nie unterschätzen den Wert von Entscheidungen basierend auf was Sie tun wissen. Manchmal ist die suboptimale Lösung optimaler als die richtige, weil Sie oder Ihr Team wissen, wie man sie besser erstellt und verwaltet als die optimale Lösung.
Eine weitere Option in der Datenbank sind Ansichten. Abhängig von Ihrem Datenbankserver können diese sehr optimal oder suboptimal oder gar nicht effektiv sein. Einer der Nachteile kann die Abfragezeit sein, je nachdem, welche Indizierungsoptionen in Ihrer Datenbank verfügbar sind. Ansichten werden zu einer noch besseren Wahl, wenn Sie keine Datenänderungen vornehmen müssen (Einfügen / Aktualisieren / Löschen).
Wenn Sie über die Datenbank hinausgehen, haben Sie die alte Bereitschaft, das Repository-Muster zu verwenden. Dies ist ein bewährter Ansatz, der sehr effektiv sein kann. Zu den Nachteilen gehört in der Regel die Kesselplatte, aber gut faktorisierte Repositorys können eine gewisse Menge davon vermeiden. Selbst wenn dies zu unglücklichen Mengen an Kesselplatte führt, handelt es sich bei den Repositorys in der Regel um einfachen Code, der einfach zu verstehen und zu warten ist sowie eine gute API darstellt /Abstraktion. Repositorys können auch für ihre Unit-Testbarkeit gut sein, die Sie durch datenbankinterne Optionen verlieren.
Es gibt Tools wie Auto-Mapper , die die Verwendung eines ORM plausibel machen, um die Übersetzung zwischen Datenbankmodellen von Orm in verwendbare Modelle zu ermöglichen. Einige dieser Tools können jedoch schwierig sein, magisches Verhalten beizubehalten / zu verstehen. Sie erstellen jedoch ein Minimum an Overhead-Code, was zu weniger Wartungsaufwand führt, wenn sie gut verstanden werden.
Als nächstes entfernen Sie sich immer weiter von der Datenbank , was bedeutet, dass es größere Mengen an Code geben wird, die sich mit dem nicht übersetzten Persistenzmodell befassen werden, was wirklich unangenehm sein wird. In diesen Szenarien geht es darum, die Übersetzungsebene in Ihre Benutzeroberfläche einzufügen, wie es sich anhört, als ob Sie dies jetzt tun. Dies ist im Allgemeinen eine sehr schlechte Idee und verfällt mit der Zeit schrecklich.
Jetzt fangen wir an, verrückt zu reden .
Das
Object
ist nicht die einzige Abstraktion, die existiert. Im Laufe der vielen Jahre, in denen Informatik studiert wurde, und noch vor dieser Zeit nach dem Studium der Mathematik, hat sich eine Fülle von Abstraktionen entwickelt. Wenn wir kreativ werden wollen, lassen Sie uns über bekannte verfügbare Abstraktionen sprechen, die untersucht wurden.Da ist das Schauspielermodell.Dies ist ein interessanter Ansatz, da Sie lediglich Nachrichten an anderen Code senden müssen, um die gesamte Arbeit effektiv an diesen anderen Code zu delegieren. Dadurch wird die Komplexität sehr effektiv von Ihrem gesamten Code getrennt. Dies könnte insofern funktionieren, als Sie eine Nachricht an einen Schauspieler mit der Aufschrift "Ich muss Obj X an Y senden" senden und an Position Y ein Behälter auf eine Antwort wartet, der dann Obj X verarbeitet. Sie können sogar eine Nachricht senden, die anweist "Ich muss Obj X und Berechnung Y, Z erledigen" und dann müssen Sie nicht einmal warten; Die Übersetzung erfolgt auf der anderen Seite des Nachrichtendurchlaufs und Sie können einfach weitermachen, wenn Sie das Ergebnis nicht lesen müssen. Dies kann ein geringfügiger Missbrauch des Darstellermodells für Ihre Zwecke sein, aber alles hängt davon ab.
Eine weitere Einkapselungsgrenze sind Prozessgrenzen. Diese können zur effektiven Trennung der Komplexität verwendet werden. Sie können den Übersetzungscode als Webdienst mit einfacher HTTP-Kommunikation mithilfe von SOAP, REST oder wenn Sie wirklich ein eigenes Protokoll möchten (nicht empfohlen) erstellen. STOMP ist insgesamt kein schlechtes neueres Protokoll. Oder verwenden Sie einen normalen Daemon-Dienst mit einer systemlokalen öffentlich zugänglichen Speicher-Pipe, um mit einem beliebigen Protokoll sehr schnell wieder zu kommunizieren. Dies hat tatsächlich einige ziemlich gute Vorteile:
Die Nachteile sind offensichtlich mehr Wartung als gewöhnlich erforderlich, und der Kommunikationsaufwand beeinträchtigt die Leistung und die Wartung.
Es gibt eine Vielzahl von Möglichkeiten, die Komplexität zusammenzufassen, damit diese Komplexität an immer seltsameren und merkwürdigeren Stellen in Ihrem System abgelegt werden kann. Mit Formen von Funktionen höherer Ordnung (oft mit Hilfe von Strategiemustern oder verschiedenen anderen ungeraden Formen von Objektmustern gefälscht) können Sie einige sehr interessante Dinge tun.
Das ist richtig, lassen Sie uns über eine Monade sprechen.Sie könnten diese Übersetzungsschicht in einer sehr unabhängigen Weise aus kleinen spezifischen Funktionen erstellen, die die erforderlichen unabhängigen Übersetzungen ausführen, aber alle nicht sichtbaren Übersetzungsfunktionen ausblenden, damit sie für externen Code kaum zugänglich sind. Dies hat den Vorteil, dass die Abhängigkeit von ihnen verringert wird und sie sich leicht ändern können, ohne großen Einfluss auf externen Code zu haben. Anschließend erstellen Sie eine Klasse, die Funktionen höherer Ordnung (anonyme Funktionen, Lambda-Funktionen, Strategieobjekte, die Sie jedoch strukturieren müssen) akzeptiert, die auf allen Objekten vom Typ OO-Modell funktionieren. Sie lassen dann den zugrunde liegenden Code, der diese Funktionen akzeptiert, die wörtliche Ausführung mit den entsprechenden Übersetzungsmethoden durchführen.
Dadurch wird eine Grenze erstellt, an der die gesamte Übersetzung nicht nur auf der anderen Seite der Grenze vorhanden ist, sondern nicht in Ihrem gesamten Code. Es wird nur auf dieser Seite verwendet, sodass der Rest Ihres Codes nur weiß, wo sich der Einstiegspunkt für diese Grenze befindet.
Ok, ja, das ist wirklich verrückt, aber wer weiß? Sie könnten einfach so verrückt sein (im Ernst, unternehmen Sie keine Monaden mit einer Verrücktheitsrate von unter 88%, es besteht die reale Gefahr von Körperverletzungen).
quelle
Mein Vorschlag:
Erstellen Sie Datenbankansichten, die:
Die Idee ist, eine Fassade zu schaffen, die ein besseres Design über dem schlechten emuliert.
Stellen Sie dann sicher, dass sich das ORM auf diese Fassade bezieht und nicht auf die realen Tabellen.
Dies vereinfacht das Einfügen jedoch nicht.
quelle
Ich kann sehen, wie Ihr vorhandenes Datenbankschema Sie veranlasst, spezifischeren Code und Abfragen für Aufgaben zu schreiben, die andernfalls mit einem besser gestalteten Schema abstrahiert würden, aber es sollte Ihre Fähigkeit, guten objektorientierten Code zu schreiben, nicht behindern.
Wenn Sie feststellen, dass Sie mit einem Datenbankschema arbeiten, das nicht perfekt ist, können Sie leicht herausfinden, auf welche Weise es Ihre Arbeit erschwert, aber irgendwann müssen Sie diese Beschwerden beiseite legen und das Beste daraus machen.
Stellen Sie sich das als Gelegenheit vor, Ihre Kreativität zu nutzen, um sauberen, wiederverwendbaren und leicht zu wartenden Code trotz des unvollkommenen Schemas zu schreiben.
quelle
Ich würde vorschlagen, SQL-sprechende Objekte zu verwenden, um Ihre ursprüngliche Frage nach besserem objektorientiertem Code zu beantworten . ORM widerspricht grundsätzlich objektorientierten Prinzipien, da es auf ein Objekt einwirkt und das Objekt in OOP eine autarke Einheit ist, die über alle Ressourcen verfügt, um sein Ziel zu erreichen. Ich bin sicher, dass dieser Ansatz Ihren Code einfacher machen könnte.
Wenn ich über den Problembereich, dh Ihre Domain, spreche, würde ich versuchen, aggregierte Wurzeln zu identifizieren . Dies sind Konsistenzgrenzen Ihrer Domain. Grenzen, die immer Bestand haben müssen. Aggregate kommunizieren über Domain-Ereignisse. Wenn Sie ein System haben, das groß genug ist, sollten Sie es wahrscheinlich auf Subsysteme aufteilen (nennen Sie es SOA, Microservice, in sich geschlossene Systeme usw.).
Ich würde auch die Verwendung von CQRS in Betracht ziehen - dies kann sowohl die Schreib- als auch die Leseseite erheblich vereinfachen. Lesen Sie unbedingt den Artikel von Udi Dahan zu diesem Thema.
quelle