Kõigepealt tõde ehk miks tuleb süsteem andmebaasi kujundusest lähtuvalt kujundada

Tere Habr!

Jätkame teema uurimist Java и kevad, sealhulgas andmebaasi tasemel. Täna kutsume teid lugema, miks peaks suurte rakenduste kujundamisel olema määrav andmebaasi struktuur, mitte Java kood, kuidas seda tehakse ja millised erandid sellest reeglist on.

Selles üsna hilises artiklis selgitan, miks ma usun, et peaaegu kõigil juhtudel peaks rakenduse andmemudel olema kujundatud pigem "andmebaasist" kui "Java võimalustest lähtuvalt" (või mis iganes kliendikeeles te kasutate). tõõtan koos). Kasutades teist lähenemisviisi, seate end pikale valu ja kannatuste teele, kui teie projekt hakkab kasvama.

Artikkel on kirjutatud selle põhjal üks küsimus, antud Stack Overflow'l.

Huvitavad arutelud redditi teemal rubriikides /r/java и /r/programmeerimine.

Koodi genereerimine

Kui üllatunud ma olin, et on nii väike osa kasutajaid, kes pärast jOOQ-iga tutvumist on nördinud selle üle, et jOOQ tugineb tõsiselt lähtekoodi genereerimisele. Keegi ei keela teil jOOQ-i oma äranägemise järgi kasutamast ega sunni teid kasutama koodi genereerimist. Kuid vaikimisi (nagu on kirjeldatud juhendis) jOOQ-ga töötamise viis on see, et alustate (pärand) andmebaasiskeemist, pöördprojekteerite selle jOOQ koodigeneraatori abil, et saada teie tabeleid esindav klasside komplekt ja seejärel kirjutate tüübi. -turvalised päringud nendele tabelitele:

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

Kood genereeritakse käsitsi väljaspool koostu või käsitsi iga koostu juures. Näiteks võib selline regenereerimine järgneda kohe pärast seda Flyway andmebaasi migratsioon, mida saab teha ka käsitsi või automaatselt.

Lähtekoodi genereerimine

Nende koodide genereerimise lähenemisviisidega - käsitsi ja automaatselt - on seotud erinevad filosoofiad, eelised ja puudused, mida ma selles artiklis üksikasjalikult ei käsitle. Kuid üldiselt on loodud koodi mõte selles, et see võimaldab meil Javas reprodutseerida seda "tõde", mida peame iseenesestmõistetavaks, kas oma süsteemis või väljaspool seda. Mõnes mõttes teevad seda kompilaatorid, kui nad genereerivad baitkoodi, masinkoodi või mõnda muud lähtekoodi vormi – me saame oma "tõe" esituse teises keeles, olenemata konkreetsetest põhjustest.

Selliseid koodigeneraatoreid on palju. Näiteks, XJC suudab XSD- või WSDL-failide põhjal luua Java-koodi. Põhimõte on alati sama:

  • Teatud tõde (sisemine või väline) on olemas – näiteks spetsifikatsioon, andmemudel vms.
  • Vajame selle tõe kohalikku esitust meie programmeerimiskeeles.

Lisaks on peaaegu alati soovitatav selline esitus genereerida, et vältida koondamist.

Tüübipakkujad ja märkuste töötlemine

Märkus: teine, kaasaegsem ja spetsiifilisem lähenemisviis jOOQ-i koodi genereerimiseks on tüübipakkujate kasutamine, nagu need on F#-s rakendatud. Sel juhul genereerib koodi kompilaator, tegelikult kompileerimisetapis. Põhimõtteliselt sellist koodi lähtekujul ei eksisteeri. Java-l on sarnased, kuigi mitte nii elegantsed tööriistad - näiteks annotatsiooniprotsessorid, Lombok.

Mõnes mõttes juhtuvad siin samad asjad, mis esimesel juhul, välja arvatud:

  • Te ei näe loodud koodi (võib-olla tundub see olukord kellelegi vähem vastumeelne?)
  • Peate tagama, et tüüpe saab esitada, st "tõene" peab olema alati saadaval. See on lihtne Lomboki puhul, mis märgib "tõde". Pisut keerulisem on see andmebaasimudelitega, mis sõltuvad pidevalt saadaolevast reaalajas ühendusest.

Mis on koodi genereerimise probleem?

Lisaks keerulisele küsimusele, kuidas koodi genereerimist kõige paremini käivitada – käsitsi või automaatselt, peame mainima ka seda, et on inimesi, kes usuvad, et koodi genereerimist pole üldse vaja. Selle seisukoha põhjenduseks, millega ma kõige sagedamini kokku puutusin, on see, et siis on ehitustorustiku püstitamine keeruline. Jah, see on tõesti raske. Tekivad täiendavad infrastruktuurikulud. Kui te alles alustate konkreetse tootega (olgu see siis jOOQ, JAXB või Hibernate vms), võtab tootmiskeskkonna seadistamine aega, mille kulutate pigem API enda õppimisele, et saaksite sellest väärtust ammutada. .

Kui generaatori struktuuri mõistmisega kaasnevad kulud on liiga suured, siis tõepoolest tegi API koodigeneraatori kasutatavuse osas halvasti (ja hiljem selgub, et ka kasutaja kohandamine selles on keeruline). Kasutatavus peaks olema iga sellise API puhul kõrgeim prioriteet. Kuid see on vaid üks argument koodi genereerimise vastu. Vastasel juhul on sisemise või välise tõe lokaalse esituse kirjutamine täiesti käsitsi.

Paljud ütlevad, et neil pole aega seda kõike teha. Nende supertoote tähtajad hakkavad otsa saama. Kunagi teeme montaažikonveierid korda, meil on aega. Vastan neile:

Kõigepealt tõde ehk miks tuleb süsteem andmebaasi kujundusest lähtuvalt kujundada
Originaal, Alan O'Rourke, Publik Stack

Kuid Hibernate/JPA-s on Java-koodi kirjutamine nii lihtne.

Tõesti. Hibernate'i ja selle kasutajate jaoks on see nii õnnistus kui ka needus. Talveunerežiimis saate lihtsalt kirjutada paar olemit, näiteks järgmiselt:

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

Ja peaaegu kõik on valmis. Nüüd on talveunerežiimi ülesanne luua keerukad "detailid" selle kohta, kuidas see üksus teie SQL-i "murde" DDL-is täpselt määratletakse:

	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äivitage rakendus. Väga lahe võimalus kiiresti alustada ja erinevaid asju proovida.

Siiski, palun lubage mul. Ma valetasin.

  • Kas talveunerežiim jõustab selle nimega primaarvõtme määratluse?
  • Kas talveunerežiim loob jaotises TITLE indeksi? – Ma tean kindlalt, et meil läheb seda vaja.
  • Kas Hibernate määrab täpselt selle võtme identiteedi spetsifikatsioonis?

Ilmselt mitte. Kui arendate oma projekti nullist, on alati mugav vana andmebaas lihtsalt ära visata ja luua uus andmebaas kohe, kui olete lisanud vajalikud märkused. Seega on raamatu olem lõpuks järgmisel kujul:

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

Lahe. Regenereerida. Jällegi, sel juhul on see alguses väga lihtne.

Kuid hiljem peate selle eest maksma

Varem või hiljem peate tootmisse minema. Siis see mudel lakkab töötamast. Sest:

Tootmises ei ole enam võimalik vajadusel vana andmebaasi ära visata ja nullist alustada. Teie andmebaas saab pärandiks.

Nüüdsest ja igavesti peate kirjutama DDL-i migratsiooniskriptid, näiteks Flyway abil. Mis juhtub sel juhul teie üksustega? Saate neid kas käsitsi kohandada (ja seega kahekordistada oma töökoormust) või käskida Hibernate'il need teie eest uuesti luua (kui tõenäoline on, et sel viisil loodud need teie ootustele vastavad?) Mõlemal juhul kaotate.

Nii et pärast tootmist vajate kuumaid plaastreid. Ja need tuleb väga kiiresti tootmisse panna. Kuna te ei valmistanud ette ega korraldanud tootmiseks üleminekut sujuvalt, lapite kõike metsikult. Ja siis pole sul enam aega kõike õigesti teha. Ja te kritiseerite talveunerežiimi, sest see on alati kellegi teise süü, mitte teie...

Selle asemel oleks võinud algusest peale asju hoopis teisiti teha. Näiteks pange jalgrattale ümmargused rattad.

Esiteks andmebaas

Tõeline "tõde" teie andmebaasi skeemis ja "suveräänsus" selle üle peitub andmebaasis. Skeem on määratletud ainult andmebaasis endas ja mitte kusagil mujal ning igal kliendil on selle skeemi koopia, seega on igati loogiline skeemile vastavuse ja selle terviklikkuse tagamine, teha seda otse andmebaasis - kus teave on ladustatud.
See on vana, isegi hakitud tarkus. Peamised ja unikaalsed võtmed on head. Võõrvõtmed on head. Piirangute kontrollimine on hea. Väited - Hästi.

Pealegi pole see veel kõik. Näiteks Oracle'i kasutamisel soovite tõenäoliselt määrata:

  • Millises lauapinnas teie laud on?
  • Mis on selle PCTFREE väärtus?
  • Kui suur on teie jada vahemälu suurus (ID taga)

See ei pruugi väikestes süsteemides olla oluline, kuid te ei pea ootama, kuni siirdute suurandmete valdkonda – saate palju varem kasu saada tarnija pakutavatest salvestusruumi optimeerimistest, nagu ülalmainitud. Ükski minu nähtud ORM-idest (sh jOOQ) ei anna juurdepääsu kõigile DDL-i valikute komplektile, mida võiksite oma andmebaasis kasutada. ORM-id pakuvad mõningaid tööriistu, mis aitavad teil DDL-i kirjutada.

Kuid päeva lõpuks kirjutatakse hästi läbimõeldud vooluahel käsitsi DDL-vormingus. Iga loodud DDL on selle ligikaudne väärtus.

Kuidas on lood kliendimudeliga?

Nagu eespool mainitud, vajate kliendil oma andmebaasi skeemi, kliendivaate koopiat. Ütlematagi selge, et see kliendivaade peab olema tegeliku mudeliga sünkroonis. Milline on parim viis selle saavutamiseks? Koodigeneraatori kasutamine.

Kõik andmebaasid pakuvad oma metateavet SQL-i kaudu. Kõikide tabelite saamiseks oma andmebaasist erinevates SQL-dialektides toimige järgmiselt.

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

Need päringud (või sarnased, olenevalt sellest, kas peate arvestama ka vaateid, materialiseeritud vaateid, tabeliväärtusega funktsioone) käivitatakse ka helistades DatabaseMetaData.getTables() JDBC-st või jOOQ metamooduli abil.

Selliste päringute tulemuste põhjal on suhteliselt lihtne luua oma andmebaasimudeli mis tahes kliendipoolset esitust, olenemata sellest, millist tehnoloogiat kliendil kasutate.

  • Kui kasutate JDBC-d või Springit, saate luua stringikonstantide komplekti
  • Kui kasutate JPA-d, saate olemid ise luua
  • Kui kasutate jOOQ-i, saate luua jOOQ-i metamudeli

Sõltuvalt sellest, kui palju funktsioone teie kliendi API (nt jOOQ või JPA) pakub, võib genereeritud metamudel olla tõeliselt rikkalik ja terviklik. Võtame näiteks kaudsete liitumiste võimaluse, kasutusele jOOQ 3.11, mis tugineb loodud metateabele teie tabelite vahel esinevate võõrvõtmesuhete kohta.

Nüüd värskendab iga andmebaasi juurdekasv automaatselt kliendi koodi. Kujutage ette näiteks:

ALTER TABLE book RENAME COLUMN title TO book_title;

Kas sa tõesti tahaksid seda tööd kaks korda teha? Mitte mingil juhul. Lihtsalt kinnitage DDL, käivitage see oma ehituskonveieri kaudu ja hankige värskendatud olem:

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

Või uuendatud jOOQ klass. Enamik DDL-i muudatusi mõjutab ka semantikat, mitte ainult süntaksit. Seetõttu võib olla kasulik vaadata kompileeritud koodi, et näha, millist koodi teie andmebaasi juurdekasv mõjutab (või võib) mõjutada.

Ainus tõde

Olenemata sellest, millist tehnoloogiat te kasutate, on alati üks mudel, mis on mõne alamsüsteemi jaoks ainuke tõeallikas – või vähemalt peaksime selle poole püüdlema ja vältima sellist ettevõttelikku segadust, kus "tõde" on kõikjal ja mitte kuskil. . Kõik võiks olla palju lihtsam. Kui vahetate lihtsalt XML-faile mõne muu süsteemiga, kasutage lihtsalt XSD-d. Vaadake jOOQ-i metamudelit INFORMATION_SCHEMA XML-vormingus:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • XSD on hästi arusaadav
  • XSD tokeniseerib XML-i sisu väga hästi ja võimaldab valideerimist kõigis kliendi keeltes
  • XSD on hästi versioonistatud ja sellel on täiustatud tagasiühilduvus
  • XSD-d saab tõlkida Java koodiks XJC abil

Viimane punkt on oluline. XML-sõnumeid kasutades välise süsteemiga suhtlemisel tahame olla kindlad, et meie sõnumid on kehtivad. Seda on JAXB, XJC ja XSD abil väga lihtne saavutada. Oleks täiesti hullumeelsus arvata, et "Java esimene" disainilahenduse korral, kus me muudame oma sõnumid Java-objektidena, saaks need kuidagi sidusalt XML-i kaardistada ja teise süsteemi tarbimiseks saata. Sel viisil genereeritud XML oleks väga halva kvaliteediga, dokumentideta ja raskesti arendatav. Kui sellise liidese jaoks oleks teenustaseme leping (SLA), keeraksime selle kohe sassi.

Ausalt öeldes juhtub see JSON API-dega kogu aeg, aga see on teine ​​lugu, ma lähen järgmine kord tülli...

Andmebaasid: need on samad

Andmebaasidega töötades mõistate, et need on kõik põhimõtteliselt sarnased. Baas omab oma andmeid ja peab skeemi haldama. Kõik skeemis tehtud muudatused tuleb rakendada otse DDL-is, nii et ühtset tõeallikat värskendatakse.

Kui allika värskendus on toimunud, peavad kõik kliendid värskendama ka oma mudeli koopiaid. Mõningaid kliente saab Java-s kirjutada, kasutades jOOQ-i ja Hibernate'i või JDBC-d (või mõlemat). Teisi kliente saab kirjutada Perlis (soovime neile lihtsalt õnne), teised aga C#-s. Vahet pole. Põhimudel on andmebaasis. ORM-ide abil loodud mudelid on tavaliselt halva kvaliteediga, halvasti dokumenteeritud ja raskesti arendatavad.

Nii et ärge tehke vigu. Ärge tehke vigu algusest peale. Töö andmebaasist. Looge kasutuselevõtu torujuhe, mida saab automatiseerida. Lubage koodigeneraatorid, et hõlbustada andmebaasimudeli kopeerimist ja klientidele salvestamist. Ja lõpetage muretsemine koodigeneraatorite pärast. Nad on head. Nendega muutute produktiivsemaks. Peate lihtsalt kulutama veidi aega nende seadistamisele algusest peale – ja siis ootab teid ees aastatepikkune suurenenud tootlikkus, mis moodustab teie projekti ajaloo.

Ära täna mind veel, hiljem.

Selgitamine

Selguse huvides: see artikkel ei propageeri mingil juhul kogu süsteemi (st domeeni, äriloogika jne jne) muutmist, et see sobiks teie andmebaasi mudeliga. Mida ma selles artiklis ütlen, on see, et kliendikood, mis andmebaasiga suhtleb, peaks toimima andmebaasi mudeli alusel, nii et see ise ei reprodutseeriks andmebaasi mudelit "esimese klassi" olekus. See loogika asub tavaliselt teie kliendi andmetele juurdepääsu kihis.

Kahetasandilistes arhitektuurides, mis on kohati veel säilinud, võib selline süsteemimudel olla ainuvõimalik. Kuid enamikus süsteemides tundub andmetele juurdepääsu kiht olevat "allsüsteem", mis kapseldab andmebaasi mudeli.

Erandid

Igal reeglil on erandeid ja ma olen juba öelnud, et andmebaasi-esimene ja lähtekoodi genereerimise lähenemisviis võib mõnikord olla sobimatu. Siin on paar sellist erandit (tõenäoliselt on teisigi):

  • Kui skeem on tundmatu ja vajab avastamist. Näiteks olete tööriista pakkuja, mis aitab kasutajatel mis tahes diagrammil navigeerida. Uhh. Siin pole koodi genereerimist. Kuid ikkagi on andmebaas esikohal.
  • Kui mõne probleemi lahendamiseks tuleb vooluringi luua. See näide näib olevat mustri pisut väljamõeldud versioon olemi atribuudi väärtus, st teil pole tegelikult selgelt määratletud skeemi. Sellisel juhul ei saa te sageli isegi kindel olla, et RDBMS teile sobib.

Erandid on oma olemuselt erandlikud. Enamikul juhtudel, mis hõlmavad RDBMS-i kasutamist, on skeem ette teada, see asub RDBMS-is ja on ainus "tõeallikas" ning kõik kliendid peavad hankima sellest tuletatud koopiad. Ideaalis peate kasutama koodigeneraatorit.

Allikas: www.habr.com

Lisa kommentaar