Totuus ensin, tai miksi järjestelmä pitää suunnitella tietokannan suunnittelun perusteella

Hei Habr!

Jatkamme aiheen tutkimista Jaava и Kevät, myös tietokantatasolla. Tänään kutsumme sinut lukemaan, miksi suuria sovelluksia suunniteltaessa tietokantarakenteen, ei Java-koodin, pitäisi olla ratkaiseva merkitys, miten tämä tehdään ja mitä poikkeuksia tähän sääntöön on.

Tässä melko myöhäisessä artikkelissa selitän, miksi uskon, että melkein kaikissa tapauksissa sovelluksen tietomalli tulisi suunnitella "tietokannasta" eikä "Javan ominaisuuksien perusteella" (tai millä tahansa asiakaskielelläsi). kanssa työskenteleminen). Ottamalla toisen lähestymistavan, asetat itsesi pitkälle kivun ja kärsimyksen polulle, kun projektisi alkaa kasvaa.

Artikkeli on kirjoitettu pohjalta yksi kysymys, annettu Stack Overflow:ssa.

Mielenkiintoisia keskusteluja redditistä osioissa /r/java и /r/ohjelmointi.

Koodin luominen

Kuinka yllättynyt olinkaan, että on niin pieni osa käyttäjiä, jotka jOOQ:iin tutustuttuaan ovat järkyttyneitä siitä, että jOOQ:n toiminta perustuu vakavasti lähdekoodin luomiseen. Kukaan ei estä sinua käyttämästä jOOQ:ta parhaaksi katsomallasi tavalla tai pakota sinua käyttämään koodin luomista. Mutta oletusarvoinen (käsikirjassa kuvattu) tapa työskennellä jOOQ:n kanssa on, että aloitat (vanhasta) tietokantaskeemasta, käännät sen käyttämällä jOOQ-koodigeneraattoria saadaksesi joukon luokkia, jotka edustavat taulukoitasi, ja kirjoitat sitten tyypin. -turvalliset kyselyt näihin taulukoihin:

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

Koodi luodaan joko manuaalisesti kokoonpanon ulkopuolella tai manuaalisesti jokaisessa kokoonpanossa. Tällainen regeneraatio voi esimerkiksi seurata välittömästi sen jälkeen Flyway-tietokannan siirto, joka voidaan tehdä myös manuaalisesti tai automaattisesti.

Lähdekoodin luominen

Näihin koodin luomiseen - manuaaliseen ja automaattiseen - liittyy useita filosofioita, etuja ja haittoja, joita en aio käsitellä yksityiskohtaisesti tässä artikkelissa. Mutta yleisesti generoidun koodin koko pointti on, että sen avulla voimme toistaa Javassa sen "totuuden", jota pidämme itsestäänselvyytenä, joko järjestelmässämme tai sen ulkopuolella. Eräässä mielessä kääntäjät tekevät näin, kun he luovat tavukoodia, konekoodia tai muuta lähdekoodin muotoa - saamme esityksen "totuudestamme" toisella kielellä, erityisistä syistä riippumatta.

Tällaisia ​​koodigeneraattoreita on monia. Esimerkiksi, XJC voi luoda Java-koodia XSD- tai WSDL-tiedostojen perusteella. Periaate on aina sama:

  • On olemassa jokin totuus (sisäinen tai ulkoinen) - esimerkiksi spesifikaatio, tietomalli jne.
  • Tarvitsemme tämän totuuden paikallisen esityksen ohjelmointikielellämme.

Lisäksi on lähes aina suositeltavaa luoda tällainen esitys redundanssin välttämiseksi.

Tyyppitoimittajat ja merkintöjen käsittely

Huomautus: toinen, nykyaikaisempi ja tarkempi lähestymistapa koodin luomiseen jOOQ:lle on käyttää tyyppitarjoajia, koska ne on toteutettu F#:ssa. Tässä tapauksessa kääntäjä luo koodin itse asiassa käännösvaiheessa. Periaatteessa tällaista koodia ei ole olemassa lähdemuodossa. Javassa on samanlaisia, vaikkakaan ei niin tyylikkäitä työkaluja - esimerkiksi merkintäprosessorit, Lombok.

Tietyssä mielessä tässä tapahtuu samat asiat kuin ensimmäisessä tapauksessa, lukuun ottamatta:

  • Et näe luotua koodia (ehkä tämä tilanne näyttää jollekin vähemmän vastenmieliseltä?)
  • Sinun on varmistettava, että tyypit voidaan toimittaa, eli "true" on aina oltava saatavilla. Tämä on helppoa Lombokin tapauksessa, joka merkitsee "totuutta". Se on hieman monimutkaisempaa tietokantamalleilla, jotka riippuvat jatkuvasti saatavilla olevasta live-yhteydestä.

Mikä koodin luomisessa on ongelmana?

Hankalan kysymyksen lisäksi, miten koodin luominen on parasta ajaa - manuaalisesti tai automaattisesti, meidän on myös mainittava, että jotkut ihmiset uskovat, että koodin luomista ei tarvita ollenkaan. Tämän useimmin törmänneen näkemyksen perusteluna on se, että silloin on vaikeaa rakentaa putkilinjaa. Kyllä, se on todella vaikeaa. Ylimääräisiä infrastruktuurikustannuksia syntyy. Jos olet vasta aloittamassa tietyn tuotteen käyttöä (olipa kyseessä jOOQ, JAXB tai Hibernate jne.), tuotantoympäristön luominen vie aikaa, jonka käytät mieluummin itse API:n oppimiseen, jotta voit poimia siitä arvoa. .

Jos generaattorin rakenteen ymmärtämiseen liittyvät kustannukset ovat liian korkeat, niin API todellakin teki huonoa työtä koodigeneraattorin käytettävyyden suhteen (ja myöhemmin käy ilmi, että myös käyttäjän räätälöinti siinä on vaikeaa). Käytettävyyden tulisi olla kaikkien tällaisten sovellusliittymien tärkein prioriteetti. Mutta tämä on vain yksi argumentti koodin luomista vastaan. Muuten on täysin manuaalista kirjoittaa paikallinen esitys sisäisestä tai ulkoisesta totuudesta.

Monet sanovat, että heillä ei ole aikaa tehdä kaikkea tätä. Heillä on loppumassa supertuotteensa määräajat. Jonakin päivänä siivoamme kokoonpanokuljettimet, meillä on aikaa. Vastaan ​​heille:

Totuus ensin, tai miksi järjestelmä pitää suunnitella tietokannan suunnittelun perusteella
Alkuperäinen, Alan O'Rourke, Audience Stack

Mutta Hibernate/JPA:ssa Java-koodin kirjoittaminen on niin helppoa.

Todella. Hibernatelle ja sen käyttäjille tämä on sekä siunaus että kirous. Hibernate-tilassa voit yksinkertaisesti kirjoittaa pari entiteettiä, kuten tämä:

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

Ja melkein kaikki on valmista. Nyt on Lepotilan tehtävä luoda monimutkaiset "yksityiskohdat" siitä, kuinka tämä entiteetti määritellään tarkalleen SQL-murtesi DDL:ssä:

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

... ja käynnistä sovellus. Todella hieno tilaisuus päästä alkuun nopeasti ja kokeilla erilaisia ​​asioita.

Salli kuitenkin. Minä valehtelin.

  • Pakottaako Hibernate tämän nimetyn ensisijaisen avaimen määritelmän?
  • Luoko Hibernate hakemiston kohteeseen TITLE? – Tiedän varmasti, että tarvitsemme sitä.
  • Tekeekö Hibernate tarkalleen tämän avaimen tunnistamisen identiteettimäärityksessä?

Luultavasti ei. Jos kehität projektiasi tyhjästä, on aina kätevää yksinkertaisesti hylätä vanha tietokanta ja luoda uusi heti, kun olet lisännyt tarvittavat huomautukset. Siten Kirja-kokonaisuus saa lopulta seuraavan muodon:

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

Viileä. Luo uudelleen. Tässä tapauksessa se on jälleen erittäin helppoa alussa.

Mutta joudut maksamaan siitä myöhemmin

Ennemmin tai myöhemmin sinun on lähdettävä tuotantoon. Silloin tämä malli lakkaa toimimasta. Koska:

Tuotannossa ei ole enää mahdollista tarvittaessa poistaa vanhaa tietokantaa ja aloittaa alusta. Tietokannastasi tulee perintö.

Tästä eteenpäin ja ikuisesti sinun on kirjoitettava DDL-siirtokomentosarjat esimerkiksi Flywayn avulla. Mitä yhteisöllesi tapahtuu tässä tapauksessa? Voit joko muokata niitä manuaalisesti (ja siten kaksinkertaistaa työmääräsi) tai voit pyytää Hibernatea luomaan ne uudelleen puolestasi (kuinka todennäköistä on, että tällä tavalla luodut vastaavat odotuksiasi?) Joka tapauksessa häviät.

Joten kun pääset tuotantoon, tarvitset hot patches. Ja ne on saatava tuotantoon erittäin nopeasti. Koska et valmistautunut etkä järjestänyt sujuvaa tuotantoprosessia, korjaat villisti kaiken. Ja sitten sinulla ei ole enää aikaa tehdä kaikkea oikein. Ja kritisoit Hibernatea, koska se on aina jonkun muun vika, et vain sinä...

Sen sijaan asiat olisi voitu tehdä aivan toisin alusta alkaen. Laita esimerkiksi pyöreät pyörät polkupyörään.

Tietokanta ensin

Todellinen "totuus" tietokantaskeemassasi ja sen "suvereniteetti" on tietokannassa. Kaavio määritellään vain tietokannassa itsessään eikä missään muualla, ja jokaisella asiakkaalla on kopio tästä skeemasta, joten on täysin järkevää valvoa skeeman ja sen eheyden noudattamista, tehdä se suoraan tietokannassa - missä tiedot ovat tallennettu.
Tämä on vanhaa, jopa hakkeroitua viisautta. Ensisijaiset ja yksilölliset avaimet ovat hyviä. Vieraat avaimet ovat hyviä. Rajoitusten tarkistaminen on hyvä asia. Väitteet - Hieno.

Lisäksi siinä ei vielä kaikki. Esimerkiksi Oraclen avulla haluat todennäköisesti määrittää:

  • Missä pöytätilassa pöytäsi on?
  • Mikä on sen PCTFREE-arvo?
  • Mikä on välimuistin koko sarjassasi (tunnuksen takana)

Tämä ei ehkä ole tärkeää pienissä järjestelmissä, mutta sinun ei tarvitse odottaa, kunnes siirryt big data -alueeseen – voit alkaa hyötyä toimittajan tarjoamista tallennusoptimoinneista, kuten yllä mainituista, paljon aikaisemmin. Mikään näkemistäni ORM-järjestelmistä (mukaan lukien jOOQ) ei tarjoa pääsyä kaikkiin DDL-vaihtoehtoihin, joita saatat haluta käyttää tietokannassasi. ORM:t tarjoavat joitain työkaluja, jotka auttavat DDL:n kirjoittamisessa.

Mutta loppujen lopuksi hyvin suunniteltu piiri kirjoitetaan käsin DDL:llä. Mikä tahansa luotu DDL on vain likiarvo siitä.

Entä asiakasmalli?

Kuten edellä mainittiin, tarvitset asiakkaalla kopion tietokantaskeemasi, asiakasnäkymästä. Tarpeetonta mainita, tämän asiakasnäkymän on oltava synkronoitu todellisen mallin kanssa. Mikä on paras tapa saavuttaa tämä? Koodigeneraattorin käyttö.

Kaikki tietokannat tarjoavat metatietonsa SQL:n kautta. Näin saat kaikki taulukot tietokannastasi eri SQL-murteilla:

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

Nämä kyselyt (tai vastaavat, riippuen siitä pitääkö ottaa huomioon myös näkymät, materialisoituneet näkymät, taulukkoarvoiset funktiot) suoritetaan myös kutsumalla DatabaseMetaData.getTables() JDBC:stä tai käyttämällä jOOQ-metamoduulia.

Tällaisten kyselyiden tuloksista on suhteellisen helppoa luoda mikä tahansa asiakaspuolen esitys tietokantamallistasi riippumatta siitä, mitä tekniikkaa asiakaskoneessa käytät.

  • Jos käytät JDBC:tä tai Springiä, voit luoda merkkijonovakioiden joukon
  • Jos käytät JPA:ta, voit luoda itse entiteetit
  • Jos käytät jOOQ:ta, voit luoda jOOQ-metamallin

Riippuen siitä, kuinka paljon toimintoja asiakassovellusliittymäsi tarjoaa (esim. jOOQ tai JPA), luotu metamalli voi olla todella rikas ja täydellinen. Otetaan esimerkiksi implisiittisten liitosten mahdollisuus, esitelty jOOQ 3.11:ssä, joka perustuu luotuihin metatietoihin taulukkojesi välisistä vierasavainsuhteista.

Nyt mikä tahansa tietokannan lisäys päivittää automaattisesti asiakaskoodin. Kuvittele esimerkiksi:

ALTER TABLE book RENAME COLUMN title TO book_title;

Haluaisitko todella tehdä tämän työn kahdesti? Ei missään tapauksessa. Sitouta DDL, suorita se koontiputken läpi ja hanki päivitetty entiteetti:

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

Tai päivitetty jOOQ-luokka. Useimmat DDL-muutokset vaikuttavat myös semantiikkaan, eivät vain syntaksiin. Siksi voi olla hyödyllistä katsoa käännetystä koodista nähdäksesi, mihin koodiin tietokannan lisäys vaikuttaa (tai voi vaikuttaa).

Ainoa totuus

Riippumatta siitä, mitä tekniikkaa käytät, aina on yksi malli, joka on ainoa totuuden lähde jollekin alajärjestelmälle - tai ainakin meidän pitäisi pyrkiä tähän ja välttää sellaista yrityksen sekaannusta, jossa "totuus" on kaikkialla eikä missään heti. . Kaikki voisi olla paljon yksinkertaisempaa. Jos vaihdat vain XML-tiedostoja jonkin muun järjestelmän kanssa, käytä XSD:tä. Katso jOOQ:n INFORMATION_SCHEMA metamallia XML-muodossa:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • XSD ymmärretään hyvin
  • XSD tokenisoi XML-sisällön erittäin hyvin ja mahdollistaa validoinnin kaikilla asiakaskielillä
  • XSD on hyvin versioitu ja siinä on edistynyt taaksepäin yhteensopivuus
  • XSD voidaan kääntää Java-koodiksi XJC:n avulla

Viimeinen kohta on tärkeä. Kun kommunikoimme ulkoisen järjestelmän kanssa XML-sanomien avulla, haluamme olla varmoja siitä, että viestimme ovat kelvollisia. Tämä on erittäin helppo saavuttaa käyttämällä JAXB:tä, XJC:tä ja XSD:tä. Olisi täysin hullua ajatella, että "Java ensin" -suunnittelulähestymistavalla, jossa teemme viestimme Java-objekteina, ne voitaisiin jotenkin johdonmukaisesti yhdistää XML:ään ja lähettää toiseen järjestelmään kulutettaviksi. Tällä tavalla luotu XML olisi erittäin huonolaatuista, dokumentoimatonta ja vaikeasti kehitettävää. Jos tällaiselle rajapinnalle olisi palvelutasosopimus (SLA), sotkemme sen välittömästi.

Rehellisesti sanottuna näin tapahtuu koko ajan JSON-sovellusliittymien kanssa, mutta se on toinen tarina, riidan ensi kerralla...

Tietokannat: ne ovat sama asia

Kun työskentelet tietokantojen kanssa, huomaat, että ne ovat kaikki pohjimmiltaan samanlaisia. Tukikohta omistaa tietonsa ja sen on hallinnoitava järjestelmää. Kaikki skeemaan tehdyt muutokset on toteutettava suoraan DDL:ssä, jotta yksittäinen totuuslähde päivitetään.

Kun lähdepäivitys on tapahtunut, kaikkien asiakkaiden on päivitettävä myös mallin kopionsa. Jotkut asiakkaat voidaan kirjoittaa Javalla käyttämällä jOOQ:ta ja Hibernatea tai JDBC:tä (tai molempia). Muut asiakkaat voidaan kirjoittaa Perlillä (toivotamme heille vain onnea), kun taas toiset voidaan kirjoittaa C#:lla. Ei sillä ole väliä. Päämalli on tietokannassa. ORM:illa luodut mallit ovat yleensä huonolaatuisia, huonosti dokumentoituja ja vaikeita kehittää.

Joten älä tee virheitä. Älä tee virheitä alusta alkaen. Työskentele tietokannasta. Rakenna käyttöönottoputki, joka voidaan automatisoida. Ota koodigeneraattorit käyttöön, jotta voit helposti kopioida tietokantamallisi ja tyhjentää sen asiakkaisiin. Ja lakkaa murehtimasta koodigeneraattoreita. He ovat hyviä. Niiden avulla sinusta tulee tuottavampi. Sinun tarvitsee vain viettää vähän aikaa niiden asettamiseen alusta alkaen - ja sitten sinua odottaa vuosien lisääntynyt tuottavuus, joka muodostaa projektisi historian.

Älä vielä kiitä minua myöhemmin.

Selvennys

Selvyyden vuoksi: Tämä artikkeli ei millään tavalla suosittele koko järjestelmän (eli toimialueen, liiketoimintalogiikan jne.) mukauttamista tietokantamalliisi. Sanon tässä artikkelissa, että tietokannan kanssa vuorovaikutuksessa olevan asiakaskoodin tulisi toimia tietokantamallin perusteella, jotta se ei itse toista tietokantamallia "ensimmäisen luokan" tilassa. Tämä logiikka sijaitsee yleensä asiakkaasi tietojen käyttökerroksessa.

Kaksitasoisissa arkkitehtuureissa, jotka ovat vielä paikoin säilyneet, tällainen järjestelmämalli voi olla ainoa mahdollinen. Kuitenkin useimmissa järjestelmissä tietojen käyttökerros näyttää minusta olevan "alijärjestelmä", joka kapseloi tietokantamallin.

poikkeukset

Jokaiseen sääntöön on poikkeuksia, ja olen jo sanonut, että tietokanta-ensimmäinen ja lähdekoodin luontitapa voi joskus olla sopimatonta. Tässä on pari tällaista poikkeusta (luultavasti muitakin):

  • Kun skeema on tuntematon ja se on löydettävä. Olet esimerkiksi toimittaja työkalulle, joka auttaa käyttäjiä navigoimaan missä tahansa kaaviossa. Oho. Tässä ei ole koodin luomista. Tietokanta on kuitenkin ensin.
  • Kun piiri on luotava lennossa jonkin ongelman ratkaisemiseksi. Tämä esimerkki näyttää hieman mielikuvitukselliselta versiolta kuviosta entiteettiattribuutin arvo, eli sinulla ei todellakaan ole selkeästi määriteltyä järjestelmää. Tässä tapauksessa et useinkaan voi olla varma, että RDBMS sopii sinulle.

Poikkeukset ovat luonteeltaan poikkeuksellisia. Useimmissa tapauksissa, joissa käytetään RDBMS:ää, skeema tunnetaan etukäteen, se sijaitsee RDBMS:n sisällä ja on ainoa "totuuden lähde", ja kaikkien asiakkaiden on hankittava siitä johdetut kopiot. Ihannetapauksessa sinun on käytettävä koodigeneraattoria.

Lähde: will.com

Lisää kommentti