TĂ”de on peamine, vĂ”i miks sĂŒsteemi tuleks projekteerida lĂ€htudes andmebaasist

Tere, Habr!

Me jÀtkame teema uurimist Java ja Spring, sealhulgas andmebaaside tasandil. TÀna pakume lugeda, miks suurte rakenduste projekteerimisel peaks andmebaasi struktuur, mitte Java kood, mÀngima mÀÀravat rolli, kuidas seda teha ja millised erandid sellest reeglist on.

Selles ĂŒsna hilises artiklis selgitan, miks arvan, et peaaegu kĂ”igis juhtudel tuleks rakenduse andmemudelit projekteerida «andmebaasist lĂ€htudes», mitte «Java vĂ”imalustest lĂ€htudes» (vĂ”i mĂ”nest muust kliendi keelest, millega te töötate). Valides teise lĂ€henemise, astud pika valude ja kannatuste teele, niipea kui su projekt hakkab kasvama.

Artikkel on kirjutatud ĂŒhe kĂŒsimuse, mille esitasid Stack Overflow's.

Huvitavad arutelud reddit'is osades /r/java ja /r/programming.

Koodigeneratsioon

Olin vĂ€ga ĂŒllatunud, et on nii vĂ€he kasutajaid, kes, tutvudes jOOQ-iga, kurdavad selle ĂŒle, et jOOQ puhul tuginedakse tĂ”siselt koodigeneratsioonile. Keegi ei takista sind kasutamast jOOQ-d nii, nagu sa arvad olevat vajalik, ja ei sunni kasutama koodigeneerimist. Kuid vaikimisi (nagu on kirjeldatud juhendis) toimub jOOQ kasutamine nii: alustad (pĂ€randatud) andmebaasi skeemist, teed selle tagasiprojekteerimise jOOQ koodigeneraatori abil, et saada klasside kogum, mis esindab sinu tabeleid, ja siis kirjutad tĂŒĂŒbikindlaid pĂ€ringuid nende tabelite jĂ€rgi:

	for (Record2 record : DSL.using(configuration)
//   ^^^^^^^^^^^^^^^^^^^^^^^ TĂŒĂŒpide teave on saadud
//   genereeritud koodi pÔhjal, millele viitab alljÀrgnev
//   SELECT tingimus
 
       .select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
//           vvvvv ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^ genereeritud nimed
       .from(ACTOR)
       .orderBy(1, 2)) {
    // ...
}

Kood genereeritakse kas kÀsitsi vÀljaspool kogumist vÔi kÀsitsi iga kogumise kÀigus. NÀiteks vÔib selline regeneerimine toimuda kohe pÀrast Flyway andmebaasi migreerimist, mida saab samuti teha kÀsitsi vÔi automaatselt..

Koodigeneratsioon

Selliste koodigeneratsiooni lĂ€henemiste – nii manuaalsete kui automaatsete – taga on erinevad filosoofiad, eelised ja puudused, millest ma ei kavatse selle artikli raames pikalt rÀÀkida. Kuid ĂŒldiselt on genereeritud koodi tuum selles, et see vĂ”imaldab Java-s esindada seda «tĂ”de», mida me peame iseenesestmĂ”istetavaks, kas meie sĂŒsteemis vĂ”i vĂ€ljaspool seda. Teatud mĂ”ttes teevad sama ka kompilaatorid, kes genereerivad vahelood, masinkoodi vĂ”i mĂ”nd muud koodi vormi, pĂ”hinedes lĂ€htekoodile – me saame esitluse meie «tĂ”est» teises keeles, sĂ”ltumata konkreetsetest pĂ”hjustest.

Selliseid koodigeneraatoreid on palju. NÀiteks XJC vÔib genereerida Java koodi XSD vÔi WSDL failide pÔhjal.Printsiip on alati sama:

  • On olemas mingi tĂ”de (sisemine vĂ”i vĂ€line) – nĂ€iteks spetsifikatsioon, andmemudel jne.
  • Me vajame selle tĂ”e kohalikke esitlusi meie programmeerimiskeeles.

Pealegi on sellise esitluse genereerimine peaaegu alati mĂ”istlik – et vĂ€ltida liigset koormust.

TĂŒĂŒpide pakkujad ja annotatsioonide töötlemine

MĂ€rkuseks: teine, kaasaegsem ja spetsiifilisem lĂ€henemine jOOQ koodigeneratsioonile on seotud tĂŒĂŒbi pakkujate kasutamisega, nagu need on rakendatud F#-is.Sel juhul genereerib koodi kompilaator tegelikult kompileerimise kĂ€igus. Sellist koodi lĂ€htekoodina ei eksisteeri. Java-s on sarnased, kuigi mitte nii elegantne tööriistad – need on annotatsiooniprotsessorid, nĂ€iteks Lombok..

Teatud mÔttes toimub siin sama, mis esimeses juhul, erandiga:

  • Sa ei nĂ€e genereeritud koodi (vĂ”ib-olla tundub see kellelegi mitte nii heidutav?)
  • Sa pead garanteerima, et tĂŒĂŒbid on kĂ€ttesaadavad, st «tĂ”de» peab olema alati kergesti kĂ€ttesaadav. See on lihtne juhul, kui Lombok, mis annotatsioonib «tĂ”e». Veidi keerulisem on andmebaasi mudelitega, mille töö sĂ”ltub pidevalt kergesti kĂ€ttesaadava elava ĂŒhenduse olemasolust.

Mis probleem on koodigeneratsiooniga?

Lisaks nutikale kĂŒsimusele selle kohta, kas koodi genereerimist peaks korraldama kĂ€sitsi vĂ”i automaatselt, tuleb mainida ka, et on inimesi, kes usuvad, et koodi genereerimine ei ole vajalik. Selle vaate kĂ”ige sagedasem pĂ”hjendus, millega ma olen kokku puutunud, on, et siis on raske seadistada kogumise konveierit. Jah, tĂ”epoolest, see on keeruline. Tekkivad tĂ€iendavad infrastruktuuri kulud. Kui alles hakkate teatud tootega (olgu see jOOQ, JAXB, vĂ”i Hibernate jne) tööle, kulub aega, mille te tahaksite pĂŒhendada API Ă”ppimisele, et hiljem sellest vÀÀrtust vĂ€lja vĂ”tta.

Kui kulud, mis on seotud genereerija töö arusaamisega, on liiga suured, siis tĂ”epoolest, on API-s halvasti tööd tehtud koodi genereerimise kasutatavuse osas (ja hiljem selgub, et ka kasutaja seadistamine on keeruline). KasutajasĂ”bralikkus peaks olema igasuguste API-de kĂ”rgeim prioriteet. Kuid see on vaid ĂŒks argument koodi genereerimise vastu. ÜlejÀÀnud osas on tĂ€ielikult kĂ€sitsi kirjutada kohalik esitlus sisemisest vĂ”i vĂ€limisest tĂ”est.

Paljud ĂŒtlevad, et neil ei ole aega sellega tegeleda. Neil on tĂ€htajad oma Super-Toote osas. Kunagi hiljem ajame konveiere korda, jĂ”uab. Ma vastan neile:

TĂ”de on peamine, vĂ”i miks sĂŒsteemi tuleks projekteerida lĂ€htudes andmebaasist
Originaal, Alan O'Rourke, Audience Stack

Aga Hibernate'i / JPA ei ole nii lihtne kirjutada koodi "Java" jaoks.

TÔepoolest. Hibernate'i jaoks on see samal ajal Ônn ja needus. Hibernate'is saab lihtsalt kirjutada paar entiteeti, nagu nÀiteks:

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

Ja peaaegu kĂ”ik on valmis. NĂŒĂŒd on Hibernate'i ĂŒlesanne genereerida keerulised "detailid" selle kohta, kuidas see entiteet mÀÀratakse teie SQL "dialekti" DDL-is:

	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 hakkame rakendust kÀivitama. TÔeliselt lahe vÔimalus, et kiiresti tööle asuda ja erinevaid asju proovida.

Kuid oodake. Ma vale rÀÀkisin.

  • Kas Hibernate rakendab tĂ”epoolest selle nimetatud primaarvĂ”tme mÀÀratlust?
  • Kas Hibernate loob indeksi TITLE-s? – ma tean kindlasti, et meil seda on vaja.
  • Kas Hibernate tĂ”epoolest muudab selle vĂ”tme identifitseerivaks Identity Specificationis?

TÔenÀoliselt mitte. Kui arendate oma projekti nullist, on alati mugav lihtsalt vana andmebaas kÔrvale visata ja genereerida uus, niipea kui lisate vajalikud annotatsioonid. Seega muutub entiteet Book lÔpuks nÔnda:

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

Lahe. Generoida uuesti. Taaskord, sel juhul on alguses vÀga lihtne.

Kuid hiljem tuleb selle eest maksta.

Varem vÔi hiljem peate minema tootmisse. Just siis lakkab see mudel toimimast. Sest:

Tootmises ei saa vanast andmebaasist vajadusel loobuda ja kÔike puhtalt lehelt alustada. Teie andmebaas muutub pÀrandiks.

NĂŒĂŒd ja igaveseks peate kirjutama DDL migratsiooniskeemid, nĂ€iteks kasutades Flyway. Mis siis juhtuma hakkab teie entiteetidega? Saate neid kas kĂ€sitsi kohandada (ja seelĂ€bi kahekordistada oma töömahtu) vĂ”i kĂ€skida Hibernate'il need uuesti genereerida (kui tĂ”enĂ€oliselt vastavad genereeritud entiteedid teie ootustele?) Igal juhul olete kaotaja.

Seega, niipea kui te lĂ€hete tootmisse, vajate kiireid plaastriteid. Ja neid tuleb tootmisse vĂ€ga kiiresti anda. Kuna te ei ole ette valmistatud ja ei ole korraldanud oma migratsioonide sujuvat konveierimist, siis patĆĄitakse kĂ”ike tohutult. Ja pĂ€rast te ei saa kĂ”ike Ă”igesti teha. Ja sĂŒĂŒdistate Hibernate'i, sest alati on sĂŒĂŒdi keegi teine, ainult mitte teie...

Selle asemel oleks saanud alustada kĂ”ike tĂ€iesti teisiti. NĂ€iteks vĂ”ite paigaldada ĂŒmarad rattad jalgratta peale.

Esiteks andmebaas.

TĂ”eline "tĂ”de" teie andmebaasi skeemis ja "suverÀÀnsus" selle ĂŒle peitub andmebaasis. Skeem mÀÀratakse ainult andmebaasis ja mitte kusagil mujal, ja igal kliendil on selle skeemi koopia, seega on tĂ€iesti mĂ”istlik rakendada skeemi ja selle terviklikkuse jĂ€rgimist otse andmebaasis – seal, kus teave asub.
See on vana, isegi kulunud tarkus. Primaar- ja unikaalsed vĂ”tmed on head. VĂ€lised vĂ”tmed – head. Piirangute kontrollimine – hea. Adekvaatsed vĂ€ited – on head.

Kuid see pole veel kÔik. NÀiteks, kui kasutate Oracle'i, tÔenÀoliselt soovite mÀÀrata:

  • Millises tabeliruumis asub teie tabel
  • Mis on selle PCTFREE vÀÀrtus
  • Mis on teie jĂ€rjestuse (ID jĂ€rgi) vahemĂ€lu suurus

VĂ”ib-olla ei ole see kĂ”ik vĂ€ikeses sĂŒsteemis oluline, kuid ei pea ootama, kuni jĂ”uate „suuremate andmete” valdkonda — saate varakult hakata lĂ”ikama kasu andmesalvestuse optimeerimistest, nagu eespool mainitud. Ükski ORM, mida olen nĂ€inud (sealhulgas jOOQ), ei paku juurdepÀÀsu tĂ€ielikule DDL valikule, mida vĂ”ite oma andmebaasis kasutada. ORM-id pakuvad mĂ”ningaid tööriistu, mis aitavad kirjutada DDL-d.

Aga lÔpuks on hÀsti kavandatud skeem kÀsitsi kirjutatud DDL-l. Iga genereeritud DDL on vaid selle ligikaudne versioon.

Ent mis teie kliendimudeli puhul?

Nagu eespool mainitud, vajate kliendis koopia oma andmebaasi skeemist, klientide vaadet. Üksikasjalikult öeldes, peab see klientide vaade olema sĂŒnkroniseeritud tegeliku mudeliga. Kuidas seda kĂ”ige paremini saavutada? Koode genereerimise abil.

KÔik andmebaasid pakuvad oma metaandmeid SQL-i kaudu. Siin on, kuidas saada oma andmebaasist kÔik tabelid erinevates SQL-i dialektides:

	-- 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 neile sarnased, sÔltuvalt sellest, kas tuleb arvestada ka vaateid, materialiseeritud vaateid, tabelivÀÀrtusega funktsioone) saab samuti teostada DatabaseMetaData.getTables() JDBC kaudu vÔi jOOQ meta-moduli kaudu.

Selliste pÀringute tulemustest on suhteliselt lihtne genereerida mis tahes kliendi vaade teie andmebaasi mudelist, sÔltumata sellest, millist tehnoloogiat kasutate oma kliendis.

  • Kui kasutate JDBC-d vĂ”i Springi, saate luua stringi konstantide komplekti.
  • Kui kasutate JPA-d, saate genereerida ise entiteedid.
  • Kui kasutate jOOQ-d, saate genereerida jOOQ meta-mudeli.

SĂ”ltuvalt teie kliendi API-st pakutavatest vĂ”imalustest (nt jOOQ vĂ”i JPA) vĂ”ib genereeritud meta-mudel olla tĂ”eliselt rikas ja tĂ€ielik. VĂ”tame nĂ€iteks vaikimisi ĂŒhendused, mis tuli jOOQ 3.11-sse, mis toetub teie tabelite vahelise vĂ€lisvĂ”tme suhete genereeritud metaandmetele.

NĂŒĂŒd toob iga andmebaasi valik automaatselt kaasa kliendi koodi vĂ€rskendamise. Kujutage nĂ€iteks ette:

ALTER TABLE book RENAME COLUMN title TO book_title;

Kas te tÔeliselt soovite seda tööd kaks korda teha? Absoluutselt mitte. Lihtsalt salvestame DDL-i, kÀivitame selle teie ehitustoru kaudu ja saame vÀrskendatud entiteedi:

@Entity
@Table(name = "book", indexes = {

  // Olete sellele mÔelnud?
  @Index(name = "i_book_title", columnList = "book_title")
})
class Book {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  int id;

  @Column("book_title")
  String bookTitle;
}

VĂ”i vĂ€rskendatud jOOQ klass. Suurem osa DDL muudatustest kajastuvad ka semantikas, mitte ainult sĂŒntaksis. SeetĂ”ttu on mugav vaadata kompileeritud koodi, milline kood vĂ”ib olla (vĂ”i on) mĂ”jutatud teie andmebaasi kasvust.

Ainus tÔde

ÜkskĂ”ik, millist tehnoloogiat te kasutate, on alati olemas ĂŒks mudel, mis on mingi alamsĂŒsteemi ainus tĂ”eallikas – vĂ”i vĂ€hemalt peaksime sellele pĂŒĂŒdlema ja vĂ€ltima sellist ettevĂ”tluse segadust, kus "tĂ”de" on korraga igal pool ja mitte kusagil. KĂ”ik vĂ”iks olla palju lihtsam. Kui te lihtsalt vahetate XML-faile mĂ”ne muu sĂŒsteemiga, kasutage lihtsalt XSD-d. Vaadake jOOQ meta-mudelit XML-vormingus:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • XSD on hĂ€sti arusaadav
  • XSD tĂ€histab XML-i sisu vĂ€ga hĂ€sti ja vĂ”imaldab valideerimist kĂ”igis kliendi keeltes
  • XSD-l on hea versioonimisel ja vĂ€lja arendatud tagurpidi ĂŒhilduvus
  • XSD-d saab Java koodi teisendada XJC abil

Viimane punkt on oluline. Suheldes vĂ€lise sĂŒsteemiga XML-sĂ”numite kaudu, tahame me olla kindlad, et meie sĂ”numid on kehtivad. Seda on vĂ€ga lihtne saavutada, kasutades JAXB, XJC ja XSD. Oleks tĂ€ielik hullumeelsus loota, et Java-pĂ”hise projekteerimise lĂ€henemise korral, kus me loome meie sĂ”numid Java objektide kujul, oleks neid vĂ”imalik kuidagi Ă”igesti XML-ile kaardistada ja saata teise sĂŒsteemi tarbimiseks. Selliselt genereeritud XML oleks vĂ€ga madala kvaliteediga, dokumenteerimata ja seda oleks raske arendada. Kui sellistele liidesele oleks olemas teenuse kvaliteedi leping (SLA), siis rikuksime me selle kohe Ă€ra.

Ausalt öeldes juhtub see pidevalt JSON API-dega, aga see on juba teine lugu, jĂ€rgmisel korral rÀÀgin


Andmebaasid: see on kÔik sama.

Töötades andmebaasidega, mĂ”istate, et need on kĂ”ik pĂ”himĂ”tteliselt sarnased. Andmebaas omab oma andmeid ja peab haldama skeemi. SĂŒsteemi muutmised peavad olema rakendatud otse DDL-le, et vĂ€rskendada ainsat tĂ”e allikat.

Kui allika uuendamine on toimunud, peavad kĂ”ik kliendid samuti vĂ€rskendama oma versioone mudelist. MĂ”ned kliendid vĂ”ivad olla kirjutatud Java-s, kasutades jOOQ-d ja Hibernate-i vĂ”i JDBC-d (vĂ”i kĂ”ike koos). Teised kliendid vĂ”ivad olla kirjutatud Perl-is (soovin neile edu), kolmandad – C#-is. See ei ole oluline. Peamine mudel asub andmebaasis. ORM-iga genereeritud mudelid on tavaliselt halva kvaliteediga, halvasti dokumenteeritud ja neid on raske arendada.

Seega Ă€rge tehke vigu. Ärge tehke vigu algusest peale. Töödelge andmebaasist lĂ€htuvalt. Looge selline juurutamisprotsess, mida on vĂ”imalik automatiseerida. Lisage koodigeneraatorid, et oleks mugav kopeerida oma andmebaasi mudelit ja edastada see klientidele. Ja lĂ”petage koodigeneraatorite pĂ€rast muretsemine. Need on head. Nende abil saate tĂ”husamaks. Tuleb lihtsalt alguses natuke aega nende seadistamiseks kulutada – ja teie ees ootavad aastaid suurenenud tootlikkust, millest hargneb teie projekti lugu.

Ärge tĂ€nage veel, hiljem.

Selgitus

Selguse huvides: see artikkel ei propageeri mingil juhul, et kogu sĂŒsteem tuleks ĂŒles ehitada andmebaasi mudeli kohaselt (st, aineala, Ă€ri loogika jne). Selle artikli mĂ”te on, et kliendi kood, mis suhtleb andmebaasiga, peaks toimima andmebaasi mudelist lĂ€htuvalt, nii et see ei peegeldaks andmebaasi mudelit 'esmaklassilisena'. Selline loogika asub tavaliselt andmebaasi juurde pÀÀsemise tasemel teie kliendil.

Kaheetapilistes arhitektuurides, mis on veel mĂ”nes kohas alles, vĂ”ib selline sĂŒsteemi mudel olla ainus vĂ”imalik. Kuid enamikus sĂŒsteemides tundub, et andme juurde pÀÀsemise tase on 'alam sĂŒsteem', mis kapseldab andmebaasi mudelit.

Erandid

Iga reegli kohta on erandeid, ja ma olen juba öelnud, et andmebaasi esmasuse ja lÀhtekoodi genereerimise lÀhenemine vÔib mÔnikord osutuda sobimatuks. Siin on paar sellist erandit (vÔimalik, et leidub ka teisi):

  • Kui skeem on teadmata ja seda tuleb avada. NĂ€iteks, kui olete tööriistade pakkuja, mis aitab kasutajatel igasugustes skeemides orienteeruda. Uff. Siin ilma koodigeneratsioonita lĂ€bi ei saa. Aga siiski – andmebaas on esikohal.
  • Kui skeem tuleb genereerida jooksvalt teatud ĂŒlesande tĂ€itmiseks. See nĂ€ide tundub olema veidi keeruline versioon mustrist entity attribute value, st teil pole tegelikult selgelt mÀÀratletud skeemi. Sellisel juhul ei saa sageli olla kindel, kas RDBMS teile sobib.

Erandid on oma olemuselt erandlikud. Enamikus RDBMS-t seotud juhtumites on skeem eelnevalt teada, see on RDBMS-is ja on ainus 'tÔe' allikas ning kÔik kliendid peavad hankima oma versioonid, mis on sellest derivaadid. Ideaalis tuleks kasutada koodigeneraatorit.

Allikas: habr.com

Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster