Wourecht éischt, oder firwat de System muss baséiert op der Datebank Struktur entworf ginn

Hey Habr!

Mir fuerderen d'Thema weider Java и Fréijoer, och um Datebankniveau. Haut invitéiere mir Iech ze liesen firwat, wann Dir grouss Uwendungen designt, et ass d'Datebankstruktur, an net de Java Code, déi entscheedend Wichtegkeet sollt sinn, wéi dëst gemaach gëtt a wéi eng Ausnahmen et zu dëser Regel sinn.

An dësem zimmlech spéiden Artikel wäert ech erklären firwat ech gleewen datt a bal alle Fäll den Datemodell an enger Applikatioun soll "aus der Datebank" entworf ginn anstatt "aus de Fäegkeeten vum Java" (oder wéi eng Client Sprooch Dir sidd schaffen mat). Andeems Dir déi zweet Approche maacht, setzt Dir Iech op e laange Wee vu Péng a Leed, wann Äre Projet ufänkt ze wuessen.

Den Artikel gouf geschriwwen baséiert op eng Fro, gëtt op Stack Overflow.

Interessant Diskussiounen iwwer reddit a Rubriken /r/java и /r/programméiere.

Code Generatioun

Wéi iwwerrascht war ech datt et sou e klenge Segment vu Benotzer ass, déi, nodeems se mat jOOQ vertraut hunn, iwwerrascht sinn iwwer d'Tatsaach datt jOOQ eescht op d'Quellcode Generatioun hänkt fir ze bedreiwen. Keen verhënnert Iech fir jOOQ ze benotzen wéi Dir passt, oder forcéiert Iech Code Generatioun ze benotzen. Awer de Standard (wéi an der Handbuch beschriwwen) Manéier fir mat jOOQ ze schaffen ass datt Dir mat engem (legacy) Datebankschema ufänkt, et ëmgedréint mat dem jOOQ Code Generator fir doduerch eng Rei vu Klassen ze kréien déi Är Dëscher representéieren, an dann Typ schreift -sécher Ufroen op dës Dëscher:

	for (Record2<String, String> record : DSL.using(configuration)
//   ^^^^^^^^^^^^^^^^^^^^^^^ Информация о типах выведена на 
//   основании сгенерированного кода, на который ссылается приведенное
// ниже условие SELECT 
 
       .select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
//           vvvvv ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^ сгенерированные имена
       .from(ACTOR)
       .orderBy(1, 2)) {
    // ...
}

De Code gëtt entweder manuell ausserhalb vun der Versammlung generéiert, oder manuell bei all Versammlung. Zum Beispill kann esou Regeneratioun direkt duerno kommen Flyway Datebank Migratioun, déi och manuell oder automatesch gemaach ka ginn.

Source Code Generatioun

Et gi verschidde Philosophien, Virdeeler an Nodeeler verbonne mat dësen Approche fir Code Generatioun - manuell an automatesch - déi ech net am Detail an dësem Artikel diskutéieren. Awer am allgemengen ass de ganze Punkt vum generéierte Code datt et eis erlaabt op Java déi "Wourecht" ze reproduzéieren déi mir selbstverständlech huelen, entweder an eisem System oder dobaussen. An engem Sënn ass dat wat Compilere maache wa se Bytecode, Maschinncode oder eng aner Form vu Quellcode generéieren - mir kréien eng Duerstellung vun eiser "Wourecht" an enger anerer Sprooch, onofhängeg vun de spezifesche Grënn.

Et gi vill esou Code Generatoren. Zum Beispill, XJC kann Java Code generéieren baséiert op XSD oder WSDL Dateien. De Prinzip ass ëmmer déiselwecht:

  • Et gëtt eng Wahrheet (intern oder extern) - zum Beispill eng Spezifizéierung, en Datemodell, asw.
  • Mir brauchen eng lokal Duerstellung vun dëser Wourecht an eiser Programméiersprooch.

Ausserdeem ass et bal ëmmer ubruecht esou eng Representatioun ze generéieren fir Redundanz ze vermeiden.

Typ Provider an Annotatioun Veraarbechtung

Notiz: eng aner, méi modern a spezifesch Approche fir Code fir jOOQ ze generéieren benotzt Typ Provider, wéi se am F# ëmgesat ginn. An dësem Fall gëtt de Code vum Compiler generéiert, tatsächlech an der Kompiléierungsstadium. Prinzipiell existéiert esou Code net a Quellform. Java huet ähnlech, awer net sou elegant Tools - Annotatiounsprozessoren, zum Beispill, Lombok.

An engem Sënn geschitt hei déiselwecht Saache wéi am éischte Fall, mat Ausnam vun:

  • Dir gesitt de generéierte Code net (vläicht schéngt dës Situatioun manner repulsiv fir een?)
  • Dir musst dofir suergen datt Zorte kënne geliwwert ginn, dat heescht, "richteg" muss ëmmer verfügbar sinn. Dëst ass einfach am Fall vu Lombok, déi "Wourecht" annotéiert. Et ass e bësse méi komplizéiert mat Datebankmodeller déi vun enger dauernd verfügbarer Liveverbindung ofhänken.

Wat ass de Problem mat Code Generatioun?

Zousätzlech zu der komplizéierter Fro wéi Dir am Beschten Code Generatioun ausféiert - manuell oder automatesch, musse mir och ernimmen datt et Leit sinn déi gleewen datt Code Generatioun guer net gebraucht gëtt. D'Begrënnung fir dee Standpunkt, deen ech am meeschten begéint hunn, ass, datt et dann schwéier ass, eng Pipeline ze bauen. Jo, et ass wierklech schwéier. Zousätzlech Infrastrukturkäschte entstoen. Wann Dir just mat engem bestëmmte Produkt ufänkt (egal ob et jOOQ ass, oder JAXB, oder Hibernate, etc.), dauert et Zäit fir e Produktiounsëmfeld opzestellen, datt Dir léiwer d'API selwer ze léieren, fir datt Dir Wäert dovun extrahéiert .

Wann d'Käschte verbonne mam Verständnis vun der Struktur vum Generator ze héich sinn, dann huet d'API eng schlecht Aarbecht iwwer d'Benotzerfrëndlechkeet vum Code Generator gemaach (a spéider stellt sech eraus datt d'Benotzerpersonaliséierung an et och komplex ass). Benotzerfrëndlechkeet soll déi héchst Prioritéit fir all esou API sinn. Awer dëst ass nëmmen een Argument géint Code Generatioun. Soss ass et absolut ganz manuell eng lokal Duerstellung vun der interner oder externer Wourecht ze schreiwen.

Vill wäert soen datt si keng Zäit hunn all dëst ze maachen. Si lafen aus Frist fir hir Super Produit. Iergendwann wäerte mir d'Montagetransporter raumen, mir hunn Zäit. Ech wäert hinnen äntweren:

Wourecht éischt, oder firwat de System muss baséiert op der Datebank Struktur entworf ginn
Original, Alan O'Rourke, Publikum Stack

Awer am Hibernate / JPA ass et sou einfach Java Code ze schreiwen.

Wierklech. Fir Wanterschlof a seng Benotzer, dëst ass souwuel e Segen an e Fluch. Am Hibernate kënnt Dir einfach e puer Entitéite schreiwen, wéi dëst:

	@Entity
class Book {
  @Id
  int id;
  String title;
}

A bal alles ass prett. Elo ass et un Hibernate fir déi komplex "Detailer" ze generéieren wéi genau dës Entitéit an der DDL vun Ärem SQL "Dialekt" definéiert gëtt:

	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);

... a fänkt d'Applikatioun un. Eng wierklech cool Geleeënheet fir séier unzefänken a verschidde Saachen ze probéieren.

Erlaabt mech awer. Ech hat gelunn.

  • Wäert Wanterschlof eigentlech d'Definitioun vun dësem genannt Primärschoul Schlëssel ëmsetzen?
  • Wäert Wanterschlof en Index am TITLE schafen? - Ech weess sécher, datt mir et brauchen.
  • Wäert Wanterschlof genee dëse Schlëssel Identifikatioun an der Identitéit Spezifizéierung maachen?

Wahrscheinlech net. Wann Dir Äre Projet vun Null entwéckelt, ass et ëmmer bequem déi al Datebank einfach ze verwerfen an eng nei ze generéieren soubal Dir déi néideg Annotatiounen bäidréit. Also wäert d'Buch Entitéit schlussendlech d'Form huelen:

	@Entity
@Table(name = "book", indexes = {
  @Index(name = "i_book_title", columnList = "title")
})
class Book {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  int id;
  String title;
}

Cool. Regeneréieren. Och an dësem Fall wäert et am Ufank ganz einfach sinn.

Mee du muss et herno bezuelen

Fréier oder spéider musst Dir an d'Produktioun goen. Dat ass wann dëse Modell ophält ze schaffen. Well:

An der Produktioun wäert et net méi méiglech sinn, wann néideg, déi al Datebank ze verwerfen a vun Null unzefänken. Är Datebank wäert Legacy ginn.

Vun elo un a fir ëmmer musst Dir schreiwen DDL Migratioun Scripten, zum Beispill, benotzt Flyway. Wat geschitt mat Ären Entitéiten an dësem Fall? Dir kënnt se entweder manuell adaptéieren (an domat Är Aarbechtslaascht duebel), oder Dir kënnt Hibernate soen fir Iech ze regeneréieren (wéi wahrscheinlech sinn déi generéiert dës Manéier fir Är Erwaardungen ze treffen?) Egal wéi, Dir verléiert.

Also wann Dir an d'Produktioun kënnt, braucht Dir waarm Flecken. A si musse ganz séier a Produktioun gesat ginn. Well Dir net virbereet hutt an net eng glat Pipeline vun Äre Migratiounen fir d'Produktioun organiséiert hutt, patch Dir alles. An dann hutt Dir keng Zäit méi alles richteg ze maachen. An Dir kritiséiert Wanterschlof, well et ëmmer een aneren d'Schold ass, just net Dir ...

Amplaz kéinten et vun Ufank un komplett anescht gemaach ginn. Zum Beispill, Ronn Rieder op engem Vëlo setzen.

Éischt Datebank

Déi richteg "Wourecht" an Ärem Datebankschema an d'"Souveränitéit" doriwwer läit an der Datebank. De Schema ass nëmmen an der Datebank selwer definéiert an soss néierens, an all Client huet eng Kopie vun dësem Schema, sou datt et perfekt Sënn mécht d'Konformitéit mam Schema a seng Integritéit ëmzesetzen, fir et richteg an der Datebank ze maachen - wou d'Informatioun ass. gespäichert.
Dëst ass al, souguer gehackte Wäisheet. Primär an eenzegaarteg Schlësselen si gutt. Auslännesch Schlësselen si gutt. Iwwerpréift Restriktiounen ass gutt. Behaaptungen - Gutt.

Ausserdeem ass dat net alles. Zum Beispill, andeems Dir Oracle benotzt, wëllt Dir wahrscheinlech spezifizéieren:

  • A wéi engem Dëschraum ass Ären Dësch?
  • Wat ass säi PCTFREE Wäert?
  • Wat ass d'Cachegréisst an Ärer Sequenz (hannert der ID)

Dëst ass vläicht net wichteg a klenge Systemer, awer Dir musst net waarden bis Dir an dat grousst Dateräich geplënnert sidd - Dir kënnt vill méi fréi profitéieren vun Ubidder geliwwert Späicheroptimiséierunge wéi déi uewen ernimmt. Keen vun den ORMen déi ech gesinn hunn (inklusiv jOOQ) bitt Zougang zu de komplette Set vun DDL Optiounen déi Dir wëllt an Ärer Datebank benotzen. ORMs bidden e puer Tools déi Iech hëllefen DDL ze schreiwen.

Awer um Enn vum Dag gëtt e gutt entworfene Circuit handgeschriwwe an DDL. All generéiert DDL ass nëmmen eng Approximatioun dovun.

Wat iwwer de Client Modell?

Wéi uewen ernimmt, op de Client braucht Dir eng Kopie vun Ärem Datebankschema, de Client Vue. Noutlosegkeet ze ernimmen, muss dës Client Vue synchroniséiert mam aktuellen Modell sinn. Wat ass de beschte Wee fir dëst z'erreechen? Benotzt e Code Generator.

All Datenbanken bidden hir Meta Informatioun iwwer SQL. Hei ass wéi Dir all Dëscher aus Ärer Datebank a verschiddene SQL Dialekter kritt:

	-- H2, HSQLDB, MySQL, PostgreSQL, SQL Server
SELECT table_schema, table_name
FROM information_schema.tables
 
-- DB2
SELECT tabschema, tabname
FROM syscat.tables
 
-- Oracle
SELECT owner, table_name
FROM all_tables
 
-- SQLite
SELECT name
FROM sqlite_master
 
-- Teradata
SELECT databasename, tablename
FROM dbc.tables

Dës Ufroen (oder ähnlech, jee nodeem ob Dir och Meenungen, materialiséierter Usiichten, Tabellewäerter Funktiounen berücksichtegen muss) ginn och duerch Uruff ausgefouert DatabaseMetaData.getTables() aus JDBC, oder benotzt de jOOQ Meta-Modul.

Aus de Resultater vun esou Ufroen ass et relativ einfach all Client-Säit Representatioun vun Ärem Datebankmodell ze generéieren, egal wéi eng Technologie Dir um Client benotzt.

  • Wann Dir JDBC oder Fréijoer benotzt, kënnt Dir e Set vu Stringkonstanten erstellen
  • Wann Dir JPA benotzt, kënnt Dir d'Entitéite selwer generéieren
  • Wann Dir jOOQ benotzt, kënnt Dir de jOOQ Meta-Modell generéieren

Ofhängeg wéi vill Funktionalitéit vun Ärem Client API ugebuede gëtt (zB jOOQ oder JPA), kann de generéierte Metamodell wierklech räich a komplett sinn. Huelt zum Beispill d'Méiglechkeet vun impliziten Bäitrëtter, agefouert an jOOQ 3.11, déi op generéiert Meta-Informatioun iwwer déi auslännesch Schlësselverhältnisser hänkt, déi tëscht Ären Dëscher existéieren.

Elo gëtt all Datebank Inkrement automatesch de Client Code aktualiséiert. Stellt Iech zum Beispill vir:

ALTER TABLE book RENAME COLUMN title TO book_title;

Wëllt Dir dës Aarbecht wierklech zweemol maachen? Op kee Fall. Engagéiert einfach d'DDL, fuert se duerch Är Build Pipeline, a kritt déi aktualiséiert Entitéit:

@Entity
@Table(name = "book", indexes = {
 
  // Вы об этом задумывались?
  @Index(name = "i_book_title", columnList = "book_title")
})
class Book {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  int id;
 
  @Column("book_title")
  String bookTitle;
}

Oder déi aktualiséiert jOOQ Klass. Déi meescht DDL Ännerungen beaflossen och Semantik, net nëmmen Syntax. Dofir kann et nëtzlech sinn am kompiléierte Code ze kucken fir ze kucken wat Code vun Ärem Datebankinkrement beaflosst (oder kéint) sinn.

Déi eenzeg Wourecht

Egal wéi eng Technologie Dir benotzt, et gëtt ëmmer e Modell deen déi eenzeg Quell vun der Wourecht fir e puer Subsystem ass - oder, op d'mannst, mir solle fir dëst ustriewen an esou Enterprise Duercherneen vermeiden, wou "Wourecht" iwwerall an néierens op eemol ass . Alles kéint vill méi einfach sinn. Wann Dir just XML Dateien mat engem anere System austauscht, benotzt just XSD. Kuckt de INFORMATION_SCHEMA Metamodell vum jOOQ an XML Form:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • XSD ass gutt verstanen
  • XSD tokenizes XML Inhalt ganz gutt an erlaabt Validatioun an all Client Sproochen
  • XSD ass gutt Versioun an huet fortgeschratt Réckkompatibilitéit
  • XSD kann an Java Code iwwersat ginn mat XJC

De leschte Punkt ass wichteg. Wann Dir mat engem externe System mat XML Messagen kommunizéiert, wëlle mir sécher sinn datt eis Messagen valabel sinn. Dëst ass ganz einfach ze erreechen mat JAXB, XJC an XSD. Et wier pure Wahnsinn ze denken datt, mat enger "Java first" Design Approche, wou mir eis Messagen als Java Objekter maachen, datt se iergendwéi kohärent op XML kartéiert kënne ginn an an en anere System fir Konsum geschéckt ginn. XML generéiert op dës Manéier wier vu ganz schlechter Qualitéit, ondokumentéiert a schwéier z'entwéckelen. Wann et e Service Level Agreement (SLA) fir esou eng Interface gëtt, wäerte mir et direkt schrauwen.

Éierlech gesot, dëst ass wat déi ganzen Zäit mat JSON APIs geschitt, awer dat ass eng aner Geschicht, ech streiden d'nächst Kéier ...

Datenbanken: si sinn déiselwecht Saach

Wann Dir mat Datenbanken schafft, mierkt Dir datt se all grondsätzlech ähnlech sinn. D'Basis besëtzt seng Donnéeën a muss de Schema verwalten. All Ännerunge fir de Schema mussen direkt an der DDL ëmgesat ginn, sou datt déi eenzeg Quell vun der Wourecht aktualiséiert gëtt.

Wann e Quellupdate geschitt ass, mussen all Clienten och hir Kopie vum Modell aktualiséieren. E puer Cliente kënnen op Java geschriwwe ginn mat jOOQ a Hibernate oder JDBC (oder béid). Aner Clientë kënnen an Perl geschriwwe ginn (mir wënschen hinnen just Gléck), anerer kënnen an C # geschriwwe ginn. Et ass egal. Den Haaptmodell ass an der Datebank. Modeller generéiert mat ORMs sinn normalerweis vu schlechter Qualitéit, schlecht dokumentéiert a schwéier z'entwéckelen.

Also maach keng Feeler. Maacht keng Feeler vun Ufank un. Aarbecht aus der Datebank. Baut eng Deployment Pipeline déi automatiséiert ka ginn. Aktivéiert Code Generatoren fir et einfach ze maachen Ären Datebankmodell ze kopéieren an op Clienten ze dumpen. A stoppen Iech Suergen iwwer Code Generatoren. Si si gutt. Mat hinnen wäert Dir méi produktiv ginn. Dir musst just e bëssen Zäit verbréngen fir se vun Ufank un opzestellen - an da waarden op Iech Joere vu verstäerkter Produktivitéit, déi d'Geschicht vun Ärem Projet ausmaachen.

Merci mir nach net, spéider.

Erklärung

Fir kloer ze sinn: Dësen Artikel plädéiert op kee Fall datt Dir de ganze System muss béien (dh Domain, Geschäftslogik, etc., etc.) fir Ären Datebankmodell ze passen. Wat ech an dësem Artikel soen ass datt de Client Code deen mat der Datebank interagéiert soll op Basis vum Datebankmodell handelen, sou datt et selwer den Datebankmodell net an engem "Éischt Klass" Status reproduzéiert. Dës Logik ass normalerweis an der Datezougangschicht op Ärem Client.

An zwee-Niveau Architekturen, déi op e puer Plazen nach preservéiert sinn, ass esou e System Modell déi eenzeg méiglech. Wéi och ëmmer, an de meeschte Systemer schéngt d'Datenzougangsschicht fir mech e "Subsystem" ze sinn, deen den Datebankmodell encapsuléiert.

Ausnahmen

Et ginn Ausnahmen op all Regel, an ech hu scho gesot datt d'Datebank-éischt a Quellcode Generatioun Approche heiansdo onpassend sinn. Hei sinn e puer vun esou Ausnahmen (et gi wahrscheinlech anerer):

  • Wann de Schema onbekannt ass a muss entdeckt ginn. Zum Beispill, Dir sidd e Fournisseur vun engem Tool dat de Benotzer hëlleft an all Diagramm ze navigéieren. Ugh. Et gëtt keng Code Generatioun hei. Awer trotzdem kënnt d'Datebank als éischt.
  • Wann e Circuit muss op der fléien generéiert ginn e puer Problem ze léisen. Dëst Beispill schéngt wéi eng liicht fantastesch Versioun vum Muster Entity Attribut Wäert, dh Dir hutt net wierklech eng kloer definéiert Schema. An dësem Fall kënnt Dir dacks net emol sécher sinn datt e RDBMS Iech passt.

Ausnahmen sinn aus der Natur aussergewéinlech. An de meeschte Fäll, déi d'Benotzung vun engem RDBMS involvéiert, ass de Schema am Viraus bekannt, et wunnt an der RDBMS an ass déi eenzeg Quell vun der "Wourecht", an all Cliente musse Kopien kréien, déi dovun ofgeleet ginn. Idealfall musst Dir e Code Generator benotzen.

Source: will.com

Setzt e Commentaire