TĂ”de eelkĂ”ige, vĂ”i miks sĂŒsteemi tuleb projekteerida, lĂ€htudes andmebaasi struktuurist

Tere, Habr!

Me jÀtkame teema uurimist Java ja Spring, sealhulgas andmebaaside tasemel. TÀna soovitame lugeda, miks suuremate rakenduste kavandamisel peaks andmebaasi struktuur, mitte Java-kood olema otsustav, kuidas seda tehakse ja millised erandid selle reegli osas on.

Selles ĂŒsna hilinenud artiklis selgitan, miks arvan, et peaaegu kĂ”igis juhtudel peaks rakenduse andmemudel olema kavandatud 'andmebaasist lĂ€htuvalt', mitte 'Java vĂ”imalustest lĂ€htuvalt' (vĂ”i muust kliendikeelest, millega töötate). Kui valite teise lĂ€henemise, astute pika valu ja kannatuste teele, kui teie projekt hakkab kasvama.

Artikkel on kirjutatud inspiratsiooniks ĂŒhele kĂŒsimusele, mille esitas Stack Overflow'is.

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

Koodi genereerimine

Ma olin hĂ€mmingus, et on nii vĂ€ike kasutajate kiht, kes, tutvudes jOOQ'ga, kurdavad selle ĂŒle, et jOOQ puhul toetutakse tĂ”siselt koodi genereerimisele. Keegi ei takista teil kasutada jOOQ'd nii, nagu soovite, ja ei sundi teid koodi genereerimist kasutama. Kuid vaike seadistuse (nagu on kirjeldatud juhendis) puhul töötab jOOQ nii: alustate (pĂ€randatud) andmebaasi skeemist, viite lĂ€bi selle tagasiprojekteerimise jOOQ koodi generaatori abil, et luua klasside kogum, mis esindab teie tabeleid, ja seejĂ€rel kirjutate tĂŒĂŒbisoodsad pĂ€ringud nende tabelite jaoks:

	for (Record2 record : DSL.using(configuration)
//   ^^^^^^^^^^^^^^^^^^^^^^^ TĂŒĂŒpide teave on tuletatud
//   genereeritud koodist, millele viidatakse allolevas
//   SELECT tingimuses

       .select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
//           vvvvv ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^ genereeritud nimed
       .from(ACTOR)
       .orderBy(1, 2)) {
    // ...
}

Kood genereeritakse kas kÀsitsi vÀljaspool koostamist vÔi kÀsitsi iga koostamise ajal. NÀiteks selline regeneratsioon vÔib toimuda kohe pÀrast Flyway andmebaasi migratsiooni, mida saab samuti teostada kÀsitsi vÔi automaatselt.

Allikakoodi genereerimine

Tugipool, kuidas koodi genereerimist – kĂ€sitsi ja automaatsete meetoditega – seostatakse erinevate filosoofiate, eeliste ja puudustega, mida ma ei kavatse selles artiklis ĂŒksikasjalikult arutada. Kuid ĂŒldiselt on genereeritud koodi tuum selles, et see vĂ”imaldab Java-s tĂ”lgendada seda „tĂ”de”, mida me vĂ”tame iseenesestmĂ”istetavaks, kas meie sĂŒsteemi raames vĂ”i vĂ€ljaspool seda. Teatud mĂ”ttes teevad sama ka kompilaatorid, genereerides baitkoodi, masinkoodi vĂ”i mĂ”ne muu koodi vormi lĂ€htedokumentide pĂ”hjal – saame meie „tĂ”e” esituse teises keeles, sĂ”ltumata konkreetsest pĂ”hjusest.

On palju selliseid koodigeneraatoreid. NÀiteks, XJC saab genereerida Java koodi XSD vÔi WSDL failide pÔhjal.PÔhimÔte on alati sama:

  • On mingi tĂ”de (sisemine vĂ”i vĂ€line) – nĂ€iteks spetsifikatsioon, andmemudel jne.
  • Me vajame kohaliku esitluse loomist selles tĂ”des meie programmeerimiskeeles.

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

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

MĂ€rkus: veel ĂŒks, kaasaegsem ja spetsiifilisem lĂ€henemine koodi genereerimiseks jOOQ jaoks, on seotud tĂŒĂŒpide pakkujate kasutamisega, nagu need on rakendatud F#-is.Sel juhul genereerib koodi kompilaator, tegelikult kompileerimise etapis. Sellise koodi lĂ€htefailid ei eksisteeri pĂ”himĂ”tteliselt. Java-s on sarnaseid, kuigi mitte nii elegantseid tööriistu – need on annotatsiooniprotsessorid, nĂ€iteks, Lombok..

Teatud mÔttes toimub siin sama, mis esimesel juhul, vÀlja arvatud:

  • Te ei nĂ€e genereeritud koodi (vĂ”ib-olla tundub see kellelegi vĂ€hem hirmutav?)
  • Te peate tagama, et tĂŒĂŒbid oleksid kergesti kĂ€ttesaadavad, see tĂ€hendab, et „tĂ”de” peab alati olema kĂ€tte saadav. See on lihtne Lomboki puhul, mis annotatsiooniga „tĂ”de” tĂ€histab. Veidi keerulisem on andmemudelitega, mille töö sĂ”ltub pidevalt kĂ€ttesaadavast elavast ĂŒhendusest.

Mis on probleem koodi genereerimisel?

Lisaks keerulisele kĂŒsimusele selle kohta, kuidas koodi genereerimist kĂ”ige paremini alustada - kĂ€sitsi vĂ”i automaatselt, tuleb mainida ka, et on inimesi, kes arvavad, et koodi genereerimine pole ĂŒldse vajalik. Selle seisukoha pĂ”hjenduseks tuuakse kĂ”ige sagedamini vĂ€lja, et siis on keeruline seadistada kogumisĂŒsteemi. Jah, see on tĂ”epoolest keeruline. Tekkivad lisainfrastruktuuri kulud. Kui alustate teatud tootega (olgu see jOOQ, JAXB vĂ”i Hibernate jne), siis kulub arenduskeskkonna seadistamisele aega, mida tahaksite pigem kasutada API tutvumiseks, et hiljem sellest vÀÀrtust vĂ€lja tuua.

Kui kulu, mis on seotud generaatori ĂŒlesehitusega, on liiga suur, siis tĂ”epoolest on API-s koodi generaatori kasutatavus halvasti tehtud (ja hiljem selgub, et ka kasutaja seadistamine on keeruline). Kasutamise mugavus peaks olema iga sellise API kĂ”rgeim prioriteet. Kuid see on vaid ĂŒks argument koodi genereerimise vastu. Muus osas on tĂ€iesti mĂ”istlik kirjutada kohalik esitus sisemisest vĂ”i vĂ€lsest tĂ”est kĂ€sitsi.

Paljud ĂŒtlevad, et neil pole aega sellega tegeleda. Neil on tĂ€htajad oma Super-Toote osas. Kord hiljem tegeleme kogumisĂŒsteemidega, jĂ”uab kĂŒll. Ma vastan neile:

TĂ”de eelkĂ”ige, vĂ”i miks sĂŒsteemi tuleb projekteerida, lĂ€htudes andmebaasi struktuurist
Originaal, Alan O’Rourk, Audience Stack

Kuid Hibernate / JPA-s on nii lihtne kirjutada koodi "Java" jaoks.

TĂ”epoolest. Hibernate'i ja selle kasutajate jaoks on see samal ajal Ă”nn ja needus. Hibernate'i puhul saab lihtsalt kirjutada paar ĂŒksust, nĂ€iteks nii:

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

Ja peaaegu kĂ”ik on valmis. NĂŒĂŒd on Hibernate'i ĂŒlesanne genereerida keerulised "detailid", kuidas see ĂŒksus tĂ€pselt mÀÀratakse teie SQL "dialekti" DDL-s:

	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 suurepÀrane vÔimalus, et kiiresti tööle asuda ja erinevaid asju katsetada.

Kuid lubage, et ma eksisin.

  • Kas Hibernate tĂ”eliselt rakendab selle nimetatud primaarvĂ”tme mÀÀratlemist?
  • Kas Hibernate loob indeksi TITLE-s? - ma tean kindlalt, et me vajame seda.
  • Ja kas Hibernate teeb selle vĂ”tmest identifitseeriva Identity Specificationi?

Ilmselt mitte. Kui arendate oma projekti nullist, on alati mugav lihtsalt vana andmebaas kÔrvaldada ja luua uus, kui olete vajalikud annotatsioonid lisanud. Nii nÀeb lÔpuks vÀlja olend Book:

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

VÀga hea. Genereeri uuesti. Taaskord, sel juhul on alguses vÀga lihtne.

Aga hiljem tuleb selle eest tasuda.

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

Tootmises ei saa te enam vajadusel vana andmebaasi kÔrvaldada ja kÔike puhtalt lehelt alustada. Teie andmebaas muutub pÀrandiks.

Edaspidi peate kirjutama DDL migratsiooniskripte, nĂ€iteks Flyway abil.. Aga mis siis juhtub teie olenditega? Saate kas kohandada neid kĂ€sitsi (ja suurendada oma töömahtu kahekordseks) vĂ”i kĂŒsida Hibernate'ilt, et ta genereeriks need uuesti teie jaoks (kui suured on vĂ”imalused, et sellisel viisil genereeritud vastab teie ootustele?) Igatahes jÀÀte kaotajaks.

Nii et niipea, kui lĂ€hete tootmisse, vajate kiireid patĆĄe. Ja neid tuleb tootmisse viia vĂ€ga kiiresti. Kuna te pole valmistunud ega organiseerinud sujuvat tootmisprotsessi oma migratsioonide jaoks, peate kĂ”ik tormiliselt patĆĄeerima. Ja siis ei jĂ”ua te kĂ”ike Ă”igesti teha. Ja sĂŒĂŒdistate Hibernate'i, kuna alati on sĂŒĂŒdi keegi muu, mitte teie...

Selle asemel oleks algusest peale saanud teha kĂ”ik hoopis teisiti. NĂ€iteks panna jalgrattale ĂŒmmargused rattad.

Esialgu andmebaas.

TĂ”eline „tĂ”de” teie andmebaasi skeemis ja „suverÀÀnsus” selle ĂŒle peitub andmebaasis endas. Skeem mÀÀratletakse ainult andmebaasis ja mitte kusagil mujal, ja igal kliendil on sellele skeemile oma koopia, seega on tĂ€iesti mĂ”istlik nĂ”uda skeemi ja selle terviklikkuse jĂ€rgimist otse andmebaasis – seal, kus teave salvestatakse.
See on vana ja isegi kulunud tarkus. Esialgsed ja unikaalsed vĂ”tmed – need on head. VĂ€listingimustes vĂ”tmed – head. Piirangute kontroll – hea. Kinnitus – on hea.

Kuid see pole veel kÔik. NÀiteks kasutades Oracle'i, soovite tÔenÀoliselt nÀidata:

  • Millises tabeliruumi teie tabel asub
  • Milline on selle PCTFREE vÀÀrtus
  • Kui suur on teie jĂ€rjestuse vahemĂ€lu (ID taga)

VĂ”ib-olla pole see kĂ”ik vĂ€ikestes sĂŒsteemides oluline, kuid ei pea ootama „suurte andmete” valdkonda sisenemist — vĂ”ib hakata varakult kasutama andmete salvestamise pakkuja poolt pakutud optimeerimist, nagu ĂŒlal mainitud. Ükski ORM, mida olen nĂ€inud (sealhulgas jOOQ), ei paku juurdepÀÀsu kogu DDL valikute komplektile, mida vĂ”iksite oma andmebaasis kasutada. ORM-id pakuvad mĂ”ningaid tööriistu, mis aitavad DDL-i kirjutada.

Aga lÔpuks, hÀsti kavandatud skeem on kÀsitsi kirjutatud DDL. Iga genereeritud DDL on lihtsalt selle lÀhedane jÀljend.

Kuidas on kliendimudeliga?

Nagu eelnevalt mainitud, vajate kliendil oma andmebaasi skeemi koopia, kliendi vaade. Üksikasjad on kindlasti paar korda mainitud, see kliendi vaade peab olema sĂŒnkroonitud tegeliku mudeliga. Kuidas seda kĂ”ige paremini saavutada? Koodi generaatori abil.

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

	-- 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, sÔltuvalt sellest, kas tuleb arvesse vÔtta ka vaateid, materialiseeritud vaateid, tabeli vÀÀrtusi funktsioone) tÀidetakse samuti kutsumisega DatabaseMetaData.getTables() JDBC-st vÔi meta-mooduli jOOQ abil.

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

  • Kui kasutate JDBC-d vĂ”i Springi, siis saate luua stringide konstantide kogumi
  • Kui kasutate JPA-d, siis vĂ”ite generiseerida ise ĂŒhikut
  • Kui kasutate jOOQ-i, siis saate genereerida jOOQ-i meta-mudeli

SÔltuvalt sellest, kui palju vÔimalusi teie kliendi API pakub (nt jOOQ vÔi JPA), vÔib genereeritud meta-mudel olla tÔeliselt rikka ja tÀielik. VÔtame nÀiteks vaikimisi liitmise vÔimaluse, mis ilmus jOOQ versioonis 3.11, mis tugineb genereeritud metaandmetele vÀliste vÔtmete suhete kohta, mis kehtivad teie tabelite vahel.

NĂŒĂŒd toob iga andmebaasi suurenemine automaatselt kaasa kliendikoodi uuendamise. Kujutage ette, nĂ€iteks:

ALTER TABLE book RENAME COLUMN title TO book_title;

Kas tÔesti soovite seda tööd kaks korda teha? Absoluutselt mitte. Lihtsalt salvestame DDL, töötleme seda teie ehitusprotsessis ja saame uuendatud entiteedi:

@Entity
@Table(name = "book", indexes = {
 
  // Kas 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 uuendatud jOOQ klass. Enamik DDL muudatusi kajastuvad ka semantikas, mitte ainult sĂŒntaksis. SeetĂ”ttu vĂ”ib olla mugav vaadata kompileeritud koodis, milline kood saab (vĂ”i vĂ”ib) teie andmebaasi suurenemise tĂ”ttu mĂ”jutatud.

Ainult ĂŒks tĂ”de

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

  • XSD on hĂ€sti arusaadav
  • XSD mĂ€rgib XML-i sisu vĂ€ga hĂ€sti ja vĂ”imaldab valideerimist kĂ”igis kliendikeeltes
  • XSD-l on hea versioonihaldus ja arenenud tagasiĂŒhilduvus
  • XSD-d saab Java koodiks tĂ”lkida kasutades XJC-d

Viimane punkt on oluline. Suheldes vĂ€list sĂŒsteemiga XML-sĂ”numite kaudu, tahame olla kindlad sĂ”numite kehtivuses. Seda on vĂ€ga lihtne saavutada JAXB, XJC ja XSD abil. Oleks tĂ€ielik hullumeelsus loota, et „Java-esimene“ lĂ€henamine, kus loome oma sĂ”numid Java objektide kujul, saaks mingil viisil mĂ”istetavalt XML-iks muuta ja edastada teise sĂŒsteemi tarbimiseks. Sellega genereeritud XML oleks vĂ€ga halva kvaliteediga, dokumenteerimata ning seda oleks keeruline arendada. Kui sellise liidese puhul oleks kvaliteedi teenuse taseme (SLA) leping, oleksime selle kohe rikkunud.

Ausalt öeldes juhtub see pidevalt JSON API-dega, kuid see on juba teine lugu, jÀrgmine kord rÀÀgin veel...

Andmebaasid: see on sama asi

Andmebaasidega töötades mĂ”istate, et need kĂ”ik on pĂ”himĂ”tteliselt sarnased. Andmebaas haldab oma andmeid ja peab juhtima skeemi. Skeemi muudatused tuleb viia ellu otse DDL-is, et uuendada ĂŒhte tĂ”e allikat.

Kui allika uuendamine on toimunud, peavad kÔik kliendid samuti oma mudelid uuendama. MÔned kliendid vÔivad olla kirjutatud Java keeles, kasutades jOOQ-d ja Hibernate'i vÔi JDBC-d (vÔi kÔiki neid korraga). Teised kliendid vÔivad olla kirjutatud Perl'is (soovime neile just head Ônne), kolmandad - C#-s. See ei ole oluline. Peamine mudel asub andmebaasis. ORM-i abil genereeritud mudelid on tavaliselt halvasti kvaliteetsed, halvasti dokumenteeritud ja neid on raske arendada.

SeetĂ”ttu Ă€rge tehke vigu. Alustage Ă”igesti. Töödelge andmebaasist lĂ€htuvalt. Looge selline juurutusprotsess, mida saab automatiseerida. Lisage koodigeneraatorid, et mugavalt kopeerida oma andmebaasi mudelit ja edastada seda klientidele. Ja lĂ”petage koodigeneraatorite muretsemine. Need on head. Nende abil saate produktiivsemaks. Peate vaid alguses natuke aega nende seadistamiseks kulutama – ja siis ootavad teid aastaid suurenenud tootlikkust, millest kujuneb teie projekti lugu.

Ärge tĂ€nage veel, hiljem.

Selgitus

Selguse huvides: see artikkel ei propageeri mingil moel, et teie andmebaasi mudel peaks kogu sĂŒsteemi painutama (st, teemaala, Ă€ri loogika jne). Selles artiklis rÀÀgin ma sellest, et kliendi kood, mis suhtleb andmebaasiga, peaks toimima andmebaasi mudelilt lĂ€htuvalt, et mitte kopeerida andmebaasi mudelit "esimese klassi" staatusesse. Selline loogika asub tavaliselt teabe juurdepÀÀsu tasemel teie kliendis.

Kaksitasandilistes arhitektuurides, mis on siiani mĂ”nes kohas sĂ€ilinud, vĂ”ib selline sĂŒsteemi mudel olla ainus vĂ”imalus. Siiski, enamikus sĂŒsteemides tundub andmete juurdepÀÀsu tase olevat „alamsĂŒsteem”, mis kapseldab endas andmebaasi mudelit.

Erandid

Igal reeglil on erandeid ja olen juba öelnud, et andmebaasi esmajÀrjekorras ja lÀhtekoodi genereerimise lÀhenemine vÔib mÔnikord olla sobimatu. Siin on paar sellist erandit (ilmselt on ka teisi):

  • Kui skeem ei ole teada ja seda tuleb avada. NĂ€iteks, kui olete tööriista pakkuja, mis aitab kasutajatel navigeerida mistahes skeemis. Uff. Siin ei saa kĂŒll ilma koodi genereerimiseta hakkama. Kuid siiski - andmebaas tuleb kĂ”igepealt.
  • Kui skeem peab jooksvalt genereerima mingi ĂŒlesande lahendamiseks. See nĂ€ide tundub olevat natuke keeruline versioon mustrist entity attribute value, st teil ei ole tĂ”eliselt mÀÀratletud skeemi. Sel juhul ei saa tihti isegi kindel olla, et teile sobib RDBMS.

Erandid on olemuselt erandlikud. Enamiku RDBMS-iga seotud juhtumite puhul on skeem ette teada, see asub RDBMS-is ja on ainus "tÔde"; kÔik kliendid peavad selle pÔhjal endale omandama koopiad, mis saadud sellest. Ideaalis tuleks kasutada koodigeneraatorit.

Allikas: habr.com

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster