In diesem sehr späten Artikel werde ich erklären, warum Sie meiner Meinung nach in den meisten Fällen bei der Entwicklung eines Datenmodells für eine Anwendung den ersten Ansatz der Datenbank befolgen müssen. Anstelle des Ansatzes „Java [jede andere Sprache] zuerst“, der Sie zu einem langen Weg voller Schmerzen und Leiden führt, sobald das Projekt zu wachsen beginnt.

"Too Busy to Be Better" Lizenzierter CC von Alan O'Rourke / Audience Stack . Originalbild
Dieser Artikel wurde von einer aktuellen StackOverflow-Frage inspiriert .
Interessante reddit Diskussionen / r / java und / r / Programmierung .
Codegenerierung
Zu meiner Überraschung scheint eine kleine Gruppe von Benutzern schockiert gewesen zu sein, dass jOOQ stark mit der Generierung von Quellcode verbunden ist.
Während Sie jOOQ genau so verwenden können, wie Sie möchten, besteht die bevorzugte Methode (gemäß Dokumentation) darin, mit dem vorhandenen Datenbankschema zu beginnen und dann die erforderlichen Clientklassen (entsprechend Ihren Tabellen) mit jOOQ zu generieren. Danach ist es einfach, typsicher zu schreiben Abfragen für diese Tabellen:
for (Record2<String, String> record : DSL.using(configuration)
Der Code kann entweder manuell außerhalb der Baugruppe oder automatisch mit jeder Baugruppe generiert werden. Eine solche Generierung kann beispielsweise unmittelbar nach der Installation von Flyway-Migrationen erfolgen , die auch manuell oder automatisch gestartet werden können.
Quellcode-Generierung
Es gibt verschiedene Philosophien, Vor- und Nachteile in Bezug auf diese Ansätze zur Codegenerierung, die ich in diesem Artikel nicht diskutieren möchte. Im Wesentlichen bedeutet der generierte Code jedoch, dass es sich um eine Java-Darstellung dessen handelt, was wir als eine Art "Standard" betrachten (sowohl innerhalb als auch außerhalb unseres Systems). In gewisser Weise machen Compiler dasselbe, wenn sie Bytecode, Maschinencode oder einen anderen Quellcode aus der Quelle generieren. Dadurch erhalten wir eine Vorstellung von unserem „Standard“ in einer anderen spezifischen Sprache.
Es gibt einige solcher Codegeneratoren. Beispielsweise kann XJC Java-Code aus XSD- oder WSDL-Dateien generieren . Das Prinzip ist immer dasselbe:
- Es gibt einige Standards (extern oder intern), wie z. B. Spezifikation, Datenmodell usw.
- Es ist notwendig, sich in unserer üblichen Programmiersprache ein Bild von diesem Standard zu machen.
Und fast immer ist es sinnvoll, diese Ansicht zu generieren , um unnötige Arbeit und unnötige Fehler zu vermeiden.
Typanbieter und Anmerkungsverarbeitung
Es ist bemerkenswert, dass ein anderer, modernerer Ansatz zur Codegenerierung in jOOQ Type Providers ist ( wie in F # ), bei dem der Code vom Compiler während der Kompilierung generiert wird und niemals in der ursprünglichen Form vorhanden ist. Ein ähnliches (aber weniger ausgefeiltes) Tool in Java sind Annotationsprozessoren wie Lombok .
In beiden Fällen ist alles das gleiche wie bei der normalen Codegenerierung, außer:
- Sie sehen den generierten Code nicht (vielleicht ist dies für viele ein großes Plus?)
- Sie müssen sicherstellen, dass Ihre „Referenz“ bei jeder Zusammenstellung verfügbar ist. Dies verursacht keine Probleme im Fall von Lombok, das den Quellcode selbst direkt kommentiert, was in diesem Fall der "Standard" ist. Etwas komplizierter bei Datenbankmodellen, die auf einer aktiven Live-Verbindung basieren.
Was ist das Problem bei der Codegenerierung?
Neben der kniffligen Frage, ob der Code manuell oder automatisch generiert werden soll, denken einige Leute, dass der Code überhaupt nicht generiert werden muss. Der Grund, den ich am häufigsten höre, ist, dass eine solche Generierung in der CI / CD-Pipeline schwer zu implementieren ist. Und ja, es ist wahr, weil Wir erhalten Overhead für die Erstellung und Unterstützung zusätzlicher Infrastrukturen, insbesondere wenn Sie mit den verwendeten Tools (jOOQ, JAXB, Hibernate usw.) noch nicht vertraut sind.
Wenn der Aufwand für das Studium des Codegenerators zu hoch ist, hat dies nur einen geringen Nutzen. Dies ist jedoch das einzige Argument dagegen. In den meisten anderen Fällen macht es absolut keinen Sinn, Code manuell zu schreiben, was die übliche Darstellung eines Modells von etwas ist.
Viele Leute behaupten, dass sie dafür keine Zeit haben, weil Im Moment müssen Sie so schnell wie möglich ein weiteres MVP einführen. Und sie werden irgendwann später in der Lage sein, ihre CI / CD-Pipeline fertigzustellen. In solchen Fällen sage ich normalerweise: "Sie sind zu beschäftigt, um besser zu werden."
"Aber Hibernate / JPA erleichtert die erste Entwicklung von Java erheblich."
Ja es ist wahr. Dies ist sowohl eine Freude als auch ein Schmerz für Benutzer im Ruhezustand. Mit ihm können Sie einfach mehrere Objekte des Formulars schreiben:
@Entity class Book { @Id int id; String title; }
Und das ist fast geschafft. Als nächstes übernimmt Hibernate die gesamte Routine zum Definieren dieses Objekts in DDL und im gewünschten SQL-Dialekt:
CREATE TABLE book ( id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, title VARCHAR(50), CONSTRAINT pk_book PRIMARY KEY (id) ); CREATE INDEX i_book_title ON book (title);
Dies ist wirklich eine großartige Möglichkeit, schnell mit der Entwicklung zu beginnen - Sie müssen nur die Anwendung starten.
Aber nicht alles ist so rosig. Es gibt noch viele Fragen:
- Wird Hibernate den Namen generieren, den ich für den Primärschlüssel benötige?
- Werde ich den Index erstellen, den ich für das Feld TITLE benötige?
- Wird für jeden Datensatz ein eindeutiger ID-Wert generiert?
Es scheint nicht. Während das Projekt entwickelt wird, können Sie Ihre aktuelle Datenbank jederzeit wegwerfen und alles von Grund auf neu generieren, indem Sie dem Modell die erforderlichen Anmerkungen hinzufügen.
Die Buchklasse in ihrer endgültigen Form sieht also ungefähr so aus:
@Entity @Table(name = "book", indexes = { @Index(name = "i_book_title", columnList = "title") }) class Book { @Id @GeneratedValue(strategy = IDENTITY) int id; String title; }
Aber Sie werden etwas später dafür bezahlen
Früher oder später wird Ihre Anwendung in Produktion genommen, und das beschriebene Schema funktioniert nicht mehr:
In einem lebendigen und realen System können Sie Ihre Datenbank nicht mehr nur abholen und ablegen, weil Daten werden darin verwendet und können viel Geld kosten.
Von nun an müssen Sie Migrationsskripte für jede Änderung im Datenmodell schreiben, z. B. mit Flyway . Was passiert jedoch mit Ihren Kundenklassen? Sie können sie entweder manuell anpassen (was zu doppelter Arbeit führt) oder Hibernate bitten, sie zu generieren (aber wie wahrscheinlich ist es, dass die Ergebnisse dieser Generierung die Erwartungen erfüllen?). Infolgedessen können Sie große Probleme erwarten.
Sobald der Code in Produktion geht, müssen fast sofort und so schnell wie möglich Korrekturen vorgenommen werden.
Und weil Die Installation von Datenbankmigrationen ist nicht in Ihre Fertigungslinie integriert. Sie müssen solche Patches manuell auf eigenes Risiko installieren. Es wird nicht genug Zeit geben, um zurück zu gehen und alles richtig zu machen. Es reicht nur aus, Hibernate für all seine Probleme verantwortlich zu machen.
Stattdessen hätten Sie von Anfang an ganz anders handeln können. Verwenden Sie nämlich runde Räder anstelle von quadratischen.
Gehen Sie zuerst zur Datenbank
Die Referenz und Steuerung des Datenschemas befindet sich im Büro Ihres DBMS. Eine Datenbank ist der einzige Ort, an dem ein Schema definiert ist, und alle Clients haben eine Kopie dieses Schemas, aber nicht umgekehrt. Die Daten befinden sich in Ihrer Datenbank und nicht in Ihrem Client. Daher ist es sinnvoll, die Kontrolle über das Schema und seine Integrität genau dort zu übernehmen, wo sich die Daten befinden.
Das ist alte Weisheit, nichts Neues. Primär- und eindeutige Schlüssel sind gut. Fremdschlüssel sind wunderschön. Das Überprüfen der Einschränkungen auf der Datenbankseite ist wunderbar. Die Behauptung (wenn sie endlich implementiert sind) ist großartig.
Und das ist noch nicht alles. Wenn Sie beispielsweise Oracle verwenden, können Sie Folgendes angeben:
- In welchem Tablespace befindet sich Ihre Tabelle?
- Was bedeutet PCTFREE?
- Wie groß ist der Sequenzcache?
Vielleicht spielt all dies auf kleinen Systemen keine Rolle, aber auf größeren Systemen müssen Sie nicht dem Pfad von "Big Data" folgen, bis Sie alle Säfte aus Ihrem aktuellen Speicher herausgepresst haben. Mit keinem einzigen ORM, den ich jemals gesehen habe (einschließlich jOOQ), können Sie den vollständigen Satz von DDL-Parametern verwenden, die Ihr DBMS bereitstellt. ORMs bieten nur einige Tools, mit denen Sie DDL schreiben können.
Letztendlich sollte ein gut gestaltetes Schema nur manuell mit einer DBMS-spezifischen DDL geschrieben werden. Alle automatisch generierten DDLs sind nur eine Annäherung daran.
Was ist mit dem Kundenmodell?
Wie bereits erwähnt, benötigen Sie eine bestimmte Darstellung des Datenbankschemas auf der Clientseite. Diese Ansicht muss natürlich mit dem realen Modell synchronisiert werden. Wie kann man das machen? Natürlich mit Codegeneratoren.
Alle Datenbanken bieten Zugriff auf ihre Metainformationen über das gute alte SQL. So können Sie beispielsweise eine Liste aller Tabellen aus verschiedenen Datenbanken abrufen:
Es sind solche Abfragen (sowie ähnliche Abfragen für Ansichten, materialisierte Ansichten und Tabellenfunktionen), die ausgeführt werden, wenn die DatabaseMetaData.getTables () -Methode eines bestimmten JDBC-Treibers aufgerufen wird, oder im jOOQ-Metamodul.
Aus den Ergebnissen solcher Abfragen ist es relativ einfach, eine Client-Darstellung des Datenbankmodells zu erstellen, unabhängig davon, welche Datenzugriffstechnologie verwendet wird.
- Wenn Sie JDBC oder Spring verwenden, können Sie eine Gruppe von String-Konstanten erstellen
- Wenn Sie JPA verwenden, können Sie Objekte selbst erstellen
- Wenn Sie jOOQ verwenden, können Sie jOOQ-Metamodelle erstellen
Abhängig von der Anzahl der Funktionen, die Ihre Datenzugriffs-API bietet (jOOQ, JPA oder etwas anderes), kann das generierte Metamodell sehr umfangreich und vollständig sein. Als Beispiel die implizite Verknüpfungsfunktion in jOOQ 3.11, die auf Metainformationen über die Beziehungen von Fremdschlüsseln zwischen Ihren Tabellen basiert .
Jetzt führt jede Änderung am Datenbankschema automatisch zu einer Aktualisierung des Client-Codes.
Stellen Sie sich vor, Sie müssen eine Spalte in einer Tabelle umbenennen:
ALTER TABLE book RENAME COLUMN title TO book_title;
Sind Sie sicher, dass Sie diese Arbeit zweimal ausführen möchten? Auf keinen Fall. Übernehmen Sie einfach diese DDL, führen Sie den Build aus und genießen Sie das aktualisierte Objekt:
@Entity @Table(name = "book", indexes = {
Außerdem muss der empfangene Client nicht jedes Mal kompiliert werden (zumindest bis zur nächsten Änderung des Datenbankschemas), was bereits ein großes Plus sein kann!
Die meisten DDL-Änderungen sind auch semantische Änderungen, nicht nur syntaktische. Daher ist es großartig, im generierten Client-Code zu sehen, welche genauen Änderungen in der Datenbank betroffen sind.
Die Wahrheit ist immer allein
Unabhängig davon, welche Technologie Sie verwenden, sollte es immer nur ein Modell geben, das der Standard für das Subsystem ist. Oder zumindest sollten wir uns darum bemühen und Verwirrung im Geschäftsleben vermeiden, wo der „Standard“ überall und nirgendwo gleichzeitig ist. Es macht alles viel einfacher. Wenn Sie beispielsweise XML-Dateien für ein anderes System freigeben, verwenden Sie wahrscheinlich XSD. Als Metamodell INFORMATION_SCHEMA jOOQ im XML-Format: https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd
- XSD ist gut verstanden
- XSD beschreibt XML-Inhalte perfekt und ermöglicht die Validierung in allen Client-Sprachen
- XSD macht die Versionierung einfach und abwärtskompatibel
- XSD kann mit XJC in Java-Code umgewandelt werden
Besonderes Augenmerk legen wir auf den letzten Punkt. Bei der Kommunikation mit einem externen System über XML-Nachrichten müssen wir uns der Gültigkeit der Nachrichten sicher sein. Und mit Dingen wie JAXB, XJC und XSD ist das wirklich sehr einfach. Es wäre verrückt, über die Angemessenheit des Java-First-Ansatzes in diesem Fall nachzudenken. Das auf der Grundlage der XML-Objekte generierte XML ist von schlechter Qualität, schlecht dokumentiert und schwer zu erweitern. Und wenn es eine SLA für eine solche Interaktion gibt, werden Sie enttäuscht sein.
Ehrlich gesagt ähnelt dies dem, was jetzt mit den verschiedenen JSON-APIs passiert, aber das ist eine ganz andere Geschichte ...
Was macht Datenbanken schlimmer?
Bei der Arbeit mit einer Datenbank ist hier alles gleich. Die Datenbank besitzt die Daten und muss auch der Master des Datenschemas sein. Alle Schemaänderungen müssen direkt über DDL vorgenommen werden, um die Referenz zu aktualisieren.
Nach der Aktualisierung der Referenz sollten alle Kunden ihre Vorstellungen zum Modell aktualisieren. Einige Clients können mit jOOQ und / oder Hibernate oder JDBC in Java geschrieben werden. Andere Kunden können in Perl (viel Glück für sie) oder sogar in C # geschrieben werden. Es spielt keine Rolle. Das Hauptmodell befindet sich in der Datenbank. Mit ORM erstellte Modelle sind zwar von schlechter Qualität, jedoch nicht gut dokumentiert und schwer zu erweitern.
Tun Sie dies daher nicht und von Anfang an. Beginnen Sie stattdessen mit einer Datenbank. Erstellen Sie eine automatisierte CI / CD-Pipeline. Verwenden Sie die Codegenerierung, um automatisch ein Datenbankmodell für Clients für jeden Build zu generieren. Und hör auf, dir Sorgen zu machen, alles wird gut. Alles, was erforderlich ist, ist ein kleiner anfänglicher Aufwand zum Aufbau der Infrastruktur, aber als Ergebnis erhalten Sie für den Rest Ihres Projekts über Jahre hinweg einen Gewinn im Entwicklungsprozess.
Nein danke.
Erklärungen
Konsolidierung: In diesem Artikel wird in keiner Weise behauptet, dass das Datenbankmodell für Ihr gesamtes System (Themenbereich, Geschäftslogik usw.) gelten sollte. Meine Aussagen bestehen nur darin, dass der mit der Datenbank interagierende Client-Code nur eine Darstellung des Datenbankschemas sein sollte, es aber in keiner Weise definieren und formen sollte.
In den zweistufigen Architekturen, die noch einen Platz haben, ist das Datenbankschema möglicherweise die einzige Informationsquelle über das Modell Ihres Systems. Auf den meisten Systemen sehe ich die Ebene des Datenzugriffs jedoch als ein „Subsystem“, das ein Datenbankmodell kapselt. Irgendwie so.
Ausnahmen
Wie bei jeder anderen guten Regel gibt es auch bei uns Ausnahmen (und ich habe bereits gewarnt, dass der erste Ansatz der Datenbank und die Codegenerierung nicht immer die richtige Wahl sind). Diese Ausnahmen (möglicherweise ist die Liste nicht vollständig):
- Wenn die Schaltung nicht im Voraus bekannt ist und untersucht werden muss. Sie sind beispielsweise Anbieter eines Tools, mit dem Benutzer in jedem Schema navigieren können. Natürlich kann es keine Codegenerierung geben. In jedem Fall müssen Sie sich jedoch mit der Datenbank selbst und ihrem Schema befassen.
- Wenn Sie für eine Aufgabe ein Schema im laufenden Betrieb erstellen müssen. Dies kann einer der Variationen des Entity-Attribut-Wert-Musters ähnlich sein, wie z Sie haben kein klar definiertes Muster. Es gibt auch keine Gewissheit, dass RDBMS in diesem Fall die richtige Wahl ist.
Die Besonderheit dieser Ausnahmen ist, dass sie sich in Wildtieren selten treffen. In den meisten Fällen ist das Schema bei Verwendung relationaler Datenbanken im Voraus bekannt und der "Standard" Ihres Modells. Clients sollten mit einer Kopie dieses Modells arbeiten, die mit Codegeneratoren erstellt wurde.