Waarheid eerste, of waarom die stelsel ontwerp moet word op grond van die databasistoestel

Haai Habr!

Ons gaan voort om die onderwerp te verken Java и Lenteinsluitend op die databasisvlak. Vandag bied ons aan om te lees oor hoekom, wanneer groot toepassings ontwerp word, dit die databasisstruktuur is, en nie die Java-kode nie, wat van deurslaggewende belang behoort te wees, hoe dit gedoen word, en wat die uitsonderings op hierdie reël is.

In hierdie taamlik laat artikel sal ek verduidelik hoekom ek dink dat die datamodel in 'n toepassing in byna alle gevalle "vanaf die databasis" eerder as "vanuit die vermoëns van Java" (of watter kliënttaal jy ook al is) ontwerp moet word. werk met). Deur die tweede benadering te kies, betree jy 'n lang pad van pyn en lyding sodra jou projek begin groei.

Die artikel is geskryf op grond van een vraag, gegee op Stack Overflow.

Interessante besprekings oor reddit in afdelings /r/java и /r/programmering.

Kode generering

Hoe verbaas is ek tog dat daar so 'n klein lagie gebruikers is wat, nadat hulle met jOOQ kennis gemaak het, vererg oor die feit dat jOOQ ernstig staatmaak op die generering van bronkode om te loop. Niemand keer jou om jOOQ te gebruik soos jy goeddink nie, en niemand dwing jou om kodegenerering te gebruik nie. Maar by verstek (soos beskryf in die handleiding), werk jOOQ so: jy begin met 'n (legacy) databasisskema, reverse engineer dit met die jOOQ-kodegenerator om 'n stel klasse te kry wat jou tabelle verteenwoordig, en skryf dan tipe- veilige navrae teen hierdie tabelle:

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

Die kode word óf met die hand buite die gebou gegenereer, óf met die hand op elke bou. So 'n regenerasie kan byvoorbeeld onmiddellik daarna volg Flyway-databasismigrasie, wat ook met die hand of outomaties gedoen kan word.

Bronkode generering

Daar is verskeie filosofieë, voordele en nadele verbonde aan hierdie benaderings tot kodegenerering – handmatig en outomaties – wat ek nie in detail in hierdie artikel gaan bespreek nie. Maar oor die algemeen is die hele punt van die gegenereerde kode dat dit jou in staat stel om die "waarheid" wat ons as vanselfsprekend aanvaar in Java te reproduseer, hetsy binne ons stelsel of daarbuite. In 'n sekere sin doen samestellers wat greepkode, masjienkode of 'n ander soort kode van bronkode genereer dieselfde ding - ons kry 'n voorstelling van ons "waarheid" in 'n ander taal, ongeag spesifieke redes.

Daar is baie sulke kode-opwekkers. Byvoorbeeld, XJC kan Java-kode genereer gebaseer op XSD- of WSDL-lêers. Die beginsel is altyd dieselfde:

  • Daar is een of ander waarheid (intern of ekstern) - byvoorbeeld 'n spesifikasie, 'n datamodel, ens.
  • Ons benodig 'n plaaslike voorstelling van hierdie waarheid in ons programmeertaal.

Boonop is dit byna altyd raadsaam om so 'n voorstelling te genereer - om oortolligheid te vermy.

Tipe verskaffers en annotasieverwerking

Let wel: 'n Ander, meer moderne en spesifieke benadering tot kodegenerering vir jOOQ behels die gebruik van tipe verskaffers, soos hulle in F# geïmplementeer word. In hierdie geval word die kode deur die samesteller gegenereer, eintlik in die samestellingstadium. In beginsel bestaan ​​sulke kode nie in die vorm van bronkodes nie. In Java is daar soortgelyke, hoewel nie so elegante, gereedskap nie - dit is byvoorbeeld annotasieverwerkers, Lombok.

In 'n sekere sin gebeur hier dieselfde dinge as in die eerste geval, behalwe:

  • Jy sien nie die gegenereerde kode nie (miskien lyk hierdie situasie nie vir iemand so afstootlik nie?)
  • Jy moet verseker dat tipes verskaf kan word, dit wil sê, "waar" moet altyd beskikbaar wees. Dit is maklik in die geval van Lombok, wat "waarheid" annoteer. Dit is 'n bietjie moeiliker met databasismodelle wat afhanklik is van 'n lewendige verbinding wat altyd beskikbaar is.

Wat is die probleem met kodegenerering?

Benewens die moeilike vraag hoe dit beter is om kodegenerering te begin – handmatig of outomaties, moet ek noem dat daar mense is wat glo dat kodegenerering glad nie nodig is nie. Die regverdiging vir hierdie standpunt, wat ek die meeste teëgekom het, is dat dit dan moeilik is om die boupyplyn op te rig. Ja, dis regtig moeilik. Daar is bykomende infrastruktuurkoste. As jy net begin met 'n spesifieke produk (of dit nou jOOQ, of JAXB, of Hibernate, ens.), neem dit tyd om 'n werkbank op te stel wat jy graag wil spandeer om die API self te leer om waarde daaruit te kry .

As die koste verbonde aan die begrip van die toestel van die kragopwekker te hoog is, dan het die API inderdaad swak werk gedoen met die bruikbaarheid van die kodegenerator (en in die toekoms blyk dit dat aanpassing daarin ook moeilik is). Bruikbaarheid moet die hoogste prioriteit wees vir so 'n API. Maar dit is net een argument teen kodegenerering. Andersins, skryf die plaaslike voorstelling van interne of eksterne waarheid heeltemal met die hand.

Baie sal sê dat hulle nie tyd het om dit alles te doen nie. Hulle is op sperdatum vir hul Super-produk. Eendag later sal ons die monteerbande fynkam, ons sal tyd hê. Ek sal hulle antwoord:

Waarheid eerste, of waarom die stelsel ontwerp moet word op grond van die databasistoestel
Original, Alan O'Rourke, gehoorstapel

Maar in Hibernate / JPA is dit so maklik om kode "in Java" te skryf.

Regtig. Vir Hibernate en sy gebruikers is dit beide 'n seën en 'n vloek. In Hibernate kan jy eenvoudig 'n paar entiteite skryf, soos volg:

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

En byna alles is gereed. Nou is die lot van Hibernate om komplekse "besonderhede" te genereer van hoe presies hierdie entiteit gedefinieer sal word in die DDL van jou "dialek" van SQL:

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

... en begin die toepassing laat loop. 'n Baie oulike kenmerk om vinnig aan die gang te kom en verskillende dinge uit te probeer.

Laat my egter. Ek het gelieg.

  • Sal Hibernate werklik die definisie van hierdie genoemde primêre sleutel afdwing?
  • Sal Hibernate 'n indeks op TITLE skep? Ek weet verseker ons het dit nodig.
  • Sal Hibernate hierdie sleutel 'n identiteitsleutel in die identiteitspesifikasie maak?

Waarskynlik nie. As jy jou projek van nuuts af ontwikkel, is dit altyd gerieflik om bloot die ou databasis weg te gooi en 'n nuwe een te genereer sodra jy die nodige aantekeninge byvoeg. Dus, die Boek-entiteit sal uiteindelik die vorm aanneem:

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

Koel. Regenereer. Weereens, in hierdie geval sal dit aan die begin baie maklik wees.

Maar jy sal later daarvoor moet betaal.

Vroeër of later sal jy in produksie moet gaan. Dit is wanneer die model ophou werk. Omdat:

In produksie sal dit nie meer moontlik wees om, indien nodig, die ou databasis weg te gooi en alles van voor af te begin nie. Jou databasis sal verander in 'n erfenis een.

Van nou af en vir altyd sal jy moet skryf DDL-migrasieskrifte, bv. deur Flyway te gebruik. En wat sal in hierdie geval met jou entiteite gebeur? Jy kan hulle óf met die hand aanpas (en jou werklading verdubbel) óf Hibernate hulle vir jou laat regenereer (hoe waarskynlik is die een wat op hierdie manier gegenereer word om aan jou verwagtinge te voldoen?) Jy verloor enige manier.

Sodra jy dus in produksie beweeg, sal jy warm kolle nodig hê. En hulle moet baie vinnig na produksie gebring word. Aangesien jy nie die gladde pypleiding van jou migrasies vir produksie voorberei en georganiseer het nie, pleister jy wild. En dan het jy nie tyd om alles reg te doen nie. En jy skel Hibernate, want dit is altyd enigiemand se skuld, maar nie jy nie ...

In plaas daarvan kon alles van die begin af heeltemal anders gedoen word. Sit byvoorbeeld ronde wiele op 'n fiets.

Databasis eerste

Die ware "waarheid" in jou databasisskema en "soewereiniteit" daaroor lê binne die databasis. Die skema word slegs in die databasis self en nêrens anders gedefinieer nie, en elkeen van die kliënte het 'n kopie van hierdie skema, so dit maak volkome sin om nakoming van die skema en sy integriteit af te dwing, om dit reg te doen in die databasis - waar die inligting gestoor word.
Dit is ou selfs afgesaagde wysheid. Primêre en unieke sleutels is goed. Buitelandse sleutels is goed. Beperkingskontrole is goed. Bewerings - Goed.

En dis nie al nie. As u byvoorbeeld Oracle gebruik, sal u waarskynlik wil spesifiseer:

  • In watter tafelspasie is jou tafel
  • Wat is haar PCTFREE waarde
  • Wat is die kasgrootte in jou volgorde (agter die ID)

Dit alles maak dalk nie saak in klein stelsels nie, maar dit is nie nodig om te wag tot die oorgang na die gebied van "groot data" nie - jy kan begin om voordeel te trek uit verskaffer-verskafde bergingsoptimalisasies, soos dié wat hierbo genoem is, baie vroeër. Nie een van die ORM's wat ek gesien het nie (insluitend jOOQ) bied toegang tot die volledige stel DDL-opsies wat jy dalk in jou databasis wil gebruik. ORM's bied 'n paar gereedskap om jou te help om DDL te skryf.

Maar aan die einde van die dag word 'n goed ontwerpte skema met die hand in DDL geskryf. Enige gegenereerde DDL is slegs 'n benadering daarvan.

Wat van die kliëntmodel?

Soos hierbo genoem, sal jy op die kliënt 'n kopie van jou databasisskema benodig, die kliëntaansig. Nodeloos om te sê, hierdie kliënt-aansig moet in ooreenstemming wees met die werklike model. Wat is die beste manier om dit te bereik? Met 'n kodegenerator.

Alle databasisse verskaf hul meta-inligting via SQL. Hier is hoe om alle tabelle in verskillende SQL-dialekte vanaf jou databasis te kry:

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

Hierdie navrae (of soortgelyke, afhangende van of jy ook aansigte, gematerialiseerde aansigte, tabelwaarde-funksies moet oorweeg) word ook uitgevoer deur te roep DatabaseMetaData.getTables() van JDBC, of ​​met behulp van die jOOQ-meta-module.

Uit die resultate van sulke navrae is dit relatief maklik om enige kliënt-kant-voorstelling van jou databasismodel te genereer, maak nie saak watter tegnologie jy op die kliënt gebruik nie.

  • As jy JDBC of Spring gebruik, kan jy 'n stel stringkonstantes skep
  • As jy JPA gebruik, kan jy die entiteite self genereer
  • As jy jOOQ gebruik, kan jy jOOQ-metamodel genereer

Afhangende van hoeveel vermoë jou kliënt API bied (bv. jOOQ of JPA), kan die gegenereerde metamodel regtig ryk en volledig wees. Neem byvoorbeeld die moontlikheid van implisiete aansluitings, bekendgestel in jOOQ 3.11, wat staatmaak op gegenereerde meta-inligting oor vreemde sleutelverhoudings tussen jou tabelle.

Nou sal enige databasistoename outomaties die kliëntkode bywerk. Stel jou byvoorbeeld voor:

ALTER TABLE book RENAME COLUMN title TO book_title;

Wil jy regtig hierdie werk twee keer doen? In geen geval nie. Ons verbind net die DDL, voer dit deur jou boupyplyn en kry die opgedateerde entiteit:

@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;
}

Of die opgedateerde jOOQ-klas. Die meeste DDL-veranderinge beïnvloed ook semantiek, nie net sintaksis nie. Daarom kan dit gerieflik wees om in die saamgestelde kode te sien watter kode geaffekteer sal (of kan word) deur die verhoging van jou databasis.

Die enigste waarheid

Ongeag watter tegnologie jy gebruik, is daar altyd een model wat die enigste bron van waarheid vir een of ander subsisteem is – of ten minste moet ons daarna streef en ondernemingsverwarring vermy waar “waarheid” oral en nêrens tegelyk is nie. Alles kan baie makliker wees. As jy net XML-lêers met 'n ander stelsel uitruil, gebruik net XSD. Kyk na jOOQ se INFORMATION_SCHEMA-metamodel in XML-vorm:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • XSD word goed verstaan
  • XSD merk XML-inhoud baie goed op en laat validering in alle kliënttale toe
  • XSD is goed weergawe en hoogs terugwaarts versoenbaar
  • XSD kan met behulp van XJC in Java-kode vertaal word

Die laaste punt is belangrik. Wanneer ons met 'n eksterne stelsel kommunikeer deur XML-boodskappe te gebruik, wil ons seker wees dat ons boodskappe geldig is. Dit is baie maklik om te bereik met JAXB, XJC en XSD. Dit sou pure waansin wees om te dink dat, in 'n Java-eerste ontwerpbenadering waar ons ons boodskappe as Java-objekte maak, hulle op een of ander manier verstaanbaar na XML weergegee kan word en vir verbruik na 'n ander stelsel gestuur kan word. Die XML wat op hierdie manier gegenereer word, sou van baie swak gehalte wees, ongedokumenteer en moeilik om te ontwikkel. As daar 'n ooreenkoms was oor die vlak van diensgehalte (SLA) op so 'n koppelvlak, sou ons dit dadelik opmors.

Om eerlik te wees, dit is presies wat die hele tyd met die JSON API gebeur, maar dit is 'n ander storie, ek sal volgende keer argumenteer ...

Databasisse: hulle is dieselfde

As u met databasisse werk, verstaan ​​u dat hulle almal basies dieselfde is. Die databasis besit sy data en moet die skema bestuur. Enige wysigings wat aan die skema gemaak word, moet direk in DDL geïmplementeer word sodat die enkele bron van waarheid opgedateer word.

Wanneer die bronopdatering plaasgevind het, moet alle kliënte ook hul kopieë van die model opdateer. Sommige kliënte kan in Java geskryf word deur jOOQ en Hibernate of JDBC (of albei) te gebruik. Ander kliënte kan in Perl geskryf word (kom ons wens hulle geluk), ander in C#. Dit maak nie saak nie. Die hoofmodel is in die databasis. ORM-gegenereerde modelle is gewoonlik van swak gehalte, swak gedokumenteer en moeilik om te ontwikkel.

Moet dus nie foute maak nie. Moenie van die begin af foute maak nie. Werk vanaf 'n databasis. Bou 'n ontplooiingspyplyn wat geoutomatiseer kan word. Aktiveer kodegenerators om jou databasismodel gerieflik te kopieer en dit op kliënte te stort. En hou op om bekommerd te wees oor kodegenerators. Hulle is goed. Met hulle sal jy meer produktief word. Al wat jy hoef te doen is om 'n bietjie tyd te spandeer om hulle reg van die begin af op te stel, en jy sal jare se verbeterde prestasie hê om jou projek se storie te bou.

Moet my later nog nie bedank nie.

Verduideliking

Om duidelik te wees: Hierdie artikel bepleit geensins dat die hele stelsel (d.w.s. domein, besigheidslogika, ens., ens.) gebuig moet word om by jou databasismodel te pas nie. Waaroor ek in hierdie artikel praat, is dat kliëntkode wat met 'n databasis in wisselwerking is, op die basis van die databasismodel moet optree sodat dit nie die databasismodel in "eersteklas"-status weergee nie. Sulke logika is gewoonlik by die datatoegangslaag op jou kliënt geleë.

In tweevlak-argitekture, wat op sommige plekke nog bewaar word, is so 'n stelselmodel dalk die enigste moontlike. In die meeste stelsels lyk die datatoegangslaag egter vir my na 'n "substelsel" wat die databasismodel omsluit.

uitsonderings

Daar is uitsonderings op elke reël, en ek het al voorheen gesê dat die databasis eerste en bronkode generering benadering soms onvanpas kan wees. Hier is 'n paar sulke uitsonderings (daar is waarskynlik ander):

  • Wanneer die skema onbekend is en oopgemaak moet word. Byvoorbeeld, jy verskaf 'n hulpmiddel om gebruikers te help om enige diagram te navigeer. Pff. Daar is geen kodegenerering hier nie. Maar tog - die databasis eerste van alles.
  • Wanneer 'n stroombaan op die vlieg gegenereer moet word om een ​​of ander probleem op te los. Hierdie voorbeeld blyk 'n effens frillende weergawe van die patroon te wees entiteit kenmerk waarde, dit wil sê, jy het nie regtig 'n goed gedefinieerde skema nie. In hierdie geval kan jy dikwels glad nie eers seker wees dat 'n RDBMS jou sal pas nie.

Uitsonderings is van nature uitsonderlik. In die meeste gevalle wat die gebruik van RDBMS behels, is die skema vooraf bekend, dit is binne die RDBMS en is die enigste bron van "waarheid", en alle kliënte moet kopieë verkry wat daaruit afgelei is. Ideaal gesproke moet dit 'n kodegenerator behels.

Bron: will.com

Voeg 'n opmerking