Najprej resnica ali zakaj je treba sistem oblikovati na podlagi strukture baze podatkov

Pozdravljeni, Habr!

Nadaljujemo z raziskovanjem teme Java и Pomladna kolekcija, tudi na ravni baze podatkov. Danes vas vabimo, da si preberete, zakaj naj bi bila pri načrtovanju velikih aplikacij odločilna struktura baze podatkov in ne koda Java, kako se to naredi in kakšne so izjeme od tega pravila.

V tem dokaj poznem članku bom razložil, zakaj menim, da bi moral biti podatkovni model v aplikaciji v skoraj vseh primerih zasnovan "iz baze podatkov" in ne "iz zmogljivosti Jave" (ali katerega koli odjemalskega jezika, ki ga uporabljate delati z). Če izberete drugi pristop, se pripravljate na dolgo pot bolečine in trpljenja, ko bo vaš projekt začel rasti.

Članek je bil napisan na podlagi eno vprašanje, podan na Stack Overflow.

Zanimive razprave o redditu v razdelkih /r/java и /r/programiranje.

Generiranje kode

Kako presenečen sem bil, da obstaja tako majhen segment uporabnikov, ki so po seznanitvi z jOOQ ogorčeni nad dejstvom, da se jOOQ resno zanaša na ustvarjanje izvorne kode. Nihče vam ne preprečuje uporabe jOOQ, kot se vam zdi primerno, ali vas sili, da uporabljate ustvarjanje kode. Toda privzeti (kot je opisano v priročniku) način dela z jOOQ je, da začnete s (podedovano) shemo baze podatkov, jo izvedete z obratnim inženiringom z generatorjem kode jOOQ, da tako dobite niz razredov, ki predstavljajo vaše tabele, in nato napišete tip -varne poizvedbe do teh tabel:

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

Koda se generira ročno zunaj sklopa ali ročno pri vsakem sklopu. Takšna regeneracija lahko na primer sledi takoj zatem Migracija baze podatkov Flyway, ki se lahko izvede tudi ročno ali samodejno.

Generiranje izvorne kode

Obstajajo različne filozofije, prednosti in slabosti, povezane s temi pristopi k ustvarjanju kode - ročno in samodejno -, o katerih v tem članku ne bom podrobno razpravljal. Toda na splošno je bistvo ustvarjene kode v tem, da nam omogoča, da v Javi reproduciramo tisto »resnico«, ki jo jemljemo za samoumevno, bodisi v našem sistemu bodisi zunaj njega. V nekem smislu je to tisto, kar počnejo prevajalniki, ko ustvarijo bajtno kodo, strojno kodo ali kakšno drugo obliko izvorne kode - dobimo predstavitev naše "resnice" v drugem jeziku, ne glede na posebne razloge.

Takšnih generatorjev kod je veliko. na primer XJC lahko ustvari kodo Java na podlagi datotek XSD ali WSDL. Načelo je vedno isto:

  • Obstaja nekaj resnice (notranje ali zunanje) - na primer specifikacija, podatkovni model itd.
  • Potrebujemo lokalno predstavitev te resnice v našem programskem jeziku.

Poleg tega je skoraj vedno priporočljivo ustvariti takšno predstavitev, da se izognemo redundanci.

Ponudniki tipov in obdelava opomb

Opomba: drug, bolj sodoben in specifičen pristop k generiranju kode za jOOQ uporablja ponudnike tipov, kot so implementirani v F#. V tem primeru kodo ustvari prevajalnik, pravzaprav v fazi prevajanja. Takšna koda v izvorni obliki načeloma ne obstaja. Java ima podobna, čeprav ne tako elegantna orodja - procesorje za opombe, npr. Lombok.

V nekem smislu se tukaj dogajajo iste stvari kot v prvem primeru, z izjemo:

  • Ne vidite ustvarjene kode (morda se komu ta situacija zdi manj zoprna?)
  • Zagotoviti morate, da je mogoče zagotoviti vrste, to pomeni, da mora biti vedno na voljo »true«. To je enostavno v primeru Lomboka, ki označuje "resnico". Malo bolj zapleteno je pri modelih baz podatkov, ki so odvisni od stalno razpoložljive povezave v živo.

Kakšna je težava pri ustvarjanju kode?

Poleg kočljivega vprašanja, kako najbolje zagnati generiranje kode - ročno ali samodejno, moramo omeniti še, da obstajajo ljudje, ki menijo, da generiranje kode sploh ni potrebno. Utemeljitev tega stališča, ki sem ga najpogosteje srečal, je, da je potem težko vzpostaviti gradbeni cevovod. Ja, res je težko. Pojavijo se dodatni stroški infrastrukture. Če šele začenjate z določenim izdelkom (naj bo to jOOQ, ali JAXB, ali Hibernate itd.), nastavitev produkcijskega okolja zahteva čas, ki bi ga raje porabili za učenje samega API-ja, da lahko iz njega izvlečete vrednost .

Če so stroški, povezani z razumevanjem strukture generatorja, previsoki, potem je API res slabo opravil uporabnost generatorja kode (pozneje se izkaže, da je tudi prilagajanje uporabnika v njem kompleksno). Uporabnost bi morala biti najvišja prioriteta za vsak tak API. Toda to je samo en argument proti ustvarjanju kode. Sicer pa je pisanje lokalne predstavitve notranje ali zunanje resnice povsem ročno.

Mnogi bodo rekli, da za vse to nimajo časa. Iztekajo se jim roki za njihov Super izdelek. Nekoč bomo pospravili montažne trakove, bomo imeli čas. Odgovoril jim bom:

Najprej resnica ali zakaj je treba sistem oblikovati na podlagi strukture baze podatkov
Original, Alan O'Rourke, občinstvo

Toda v Hibernate/JPA je tako preprosto napisati kodo Java.

res. Za Hibernate in njegove uporabnike je to hkrati blagoslov in prekletstvo. V Hibernate lahko preprosto napišete nekaj entitet, kot je ta:

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

In skoraj vse je pripravljeno. Zdaj je na Hibernateu, da ustvari zapletene "podrobnosti" o tem, kako natančno bo ta entiteta definirana v DDL vašega "narečja" 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);

... in zaženite aplikacijo. Res odlična priložnost za hiter začetek in preizkušanje različnih stvari.

Vendar mi prosim dovolite. lagal sem.

  • Ali bo Hibernate dejansko uveljavil definicijo tega imenovanega primarnega ključa?
  • Ali bo Hibernate ustvaril indeks v TITLE? – Zagotovo vem, da ga bomo potrebovali.
  • Ali bo Hibernate natančno določil ta ključ v specifikaciji identitete?

Verjetno ne. Če svoj projekt razvijate iz nič, je vedno priročno preprosto zavreči staro zbirko podatkov in ustvariti novo, takoj ko dodate potrebne opombe. Tako bo entiteta knjige na koncu prevzela obliko:

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

Kul. Regeneriraj. Tudi v tem primeru bo na začetku zelo enostavno.

Toda pozneje boste morali plačati

Prej ali slej boste morali iti v proizvodnjo. Takrat bo ta model prenehal delovati. Ker:

V produkciji ne bo več mogoče po potrebi zavreči stare baze podatkov in začeti iz nič. Vaša zbirka podatkov bo postala dediščina.

Od zdaj naprej in za vedno boste morali pisati Skripte za selitev DDL, na primer z uporabo Flyway. Kaj se bo v tem primeru zgodilo z vašimi entitetami? Lahko jih prilagodite ročno (in tako podvojite svojo delovno obremenitev) ali pa naročite Hibernate, naj jih regenerira namesto vas (kako verjetno je, da bodo tako ustvarjeni izpolnili vaša pričakovanja?) V vsakem primeru izgubite.

Torej, ko začnete proizvodnjo, boste potrebovali vroče popravke. In v proizvodnjo jih je treba dati zelo hitro. Ker niste pripravili in niste organizirali gladkega cevovoda vaših migracij za produkcijo, vse divje krpate. In potem nimate več časa, da bi vse naredili pravilno. In kritiziraš Hibernate, ker je vedno nekdo drug kriv, samo ne ti ...

Namesto tega bi lahko že od samega začetka stvari naredili povsem drugače. Na primer, postavite okrogla kolesa na kolo.

Najprej baza podatkov

Prava "resnica" v shemi vaše baze podatkov in "suverenost" nad njo se skrivata v bazi podatkov. Shema je definirana le v sami zbirki podatkov in nikjer drugje in vsak odjemalec ima kopijo te sheme, zato je popolnoma smiselno uveljaviti skladnost s shemo in njeno celovitostjo, da to storite kar v zbirki podatkov – tam, kjer so informacije shranjeno.
To je stara, celo orabljena modrost. Primarni in edinstveni ključi so dobri. Tuji ključi so dobri. Preverjanje omejitev je dobro. Trditve - Globa.

Poleg tega to še ni vse. Na primer, če uporabljate Oracle, bi verjetno želeli podati:

  • V katerem namiznem prostoru je vaša miza?
  • Kakšna je njegova vrednost PCTFREE?
  • Kakšna je velikost predpomnilnika v vašem zaporedju (za ID-jem)

To morda ni pomembno v majhnih sistemih, vendar vam ni treba čakati, da se premaknete v področje velikih podatkov – veliko prej lahko začnete izkoriščati optimizacije shranjevanja, ki jih zagotavlja prodajalec, kot so zgoraj omenjene. Nobeden od ORM-jev, ki sem jih videl (vključno z jOOQ), ne omogoča dostopa do celotnega nabora možnosti DDL, ki bi jih morda želeli uporabiti v svoji bazi podatkov. ORM ponuja nekaj orodij, ki vam pomagajo pri pisanju DDL.

Toda na koncu dneva je dobro zasnovano vezje ročno napisano v DDL. Vsak ustvarjen DDL je le njegov približek.

Kaj pa model stranke?

Kot je navedeno zgoraj, boste na odjemalcu potrebovali kopijo sheme svoje baze podatkov, pogled odjemalca. Ni treba posebej omenjati, da mora biti ta pogled odjemalca sinhroniziran z dejanskim modelom. Kateri je najboljši način, da to dosežete? Uporaba generatorja kode.

Vse baze podatkov zagotavljajo svoje meta informacije prek SQL. Tukaj je opisano, kako pridobite vse tabele iz vaše zbirke podatkov v različnih narečjih SQL:

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

Te poizvedbe (ali podobne, odvisno od tega, ali morate upoštevati tudi poglede, materializirane poglede, funkcije z vrednostjo tabele) se prav tako izvedejo s klicem DatabaseMetaData.getTables() iz JDBC ali z uporabo metamodula jOOQ.

Iz rezultatov takšnih poizvedb je razmeroma enostavno ustvariti kakršno koli predstavitev vašega modela baze podatkov na strani odjemalca, ne glede na to, katero tehnologijo uporabljate na odjemalcu.

  • Če uporabljate JDBC ali Spring, lahko ustvarite nabor nizovnih konstant
  • Če uporabljate JPA, lahko ustvarite same entitete
  • Če uporabljate jOOQ, lahko ustvarite metamodel jOOQ

Odvisno od tega, koliko funkcionalnosti ponuja API vašega odjemalca (npr. jOOQ ali JPA), je lahko ustvarjeni meta model resnično bogat in popoln. Vzemimo na primer možnost implicitnih združevanj, uveden v jOOQ 3.11, ki temelji na ustvarjenih meta informacijah o odnosih tujih ključev, ki obstajajo med vašimi tabelami.

Zdaj bo vsako povečanje baze podatkov samodejno posodobilo kodo odjemalca. Predstavljajte si na primer:

ALTER TABLE book RENAME COLUMN title TO book_title;

Bi res želeli to delo opravljati dvakrat? V nobenem primeru. Preprosto potrdite DDL, ga poženite skozi cevovod gradnje in pridobite posodobljeno entiteto:

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

Ali posodobljen razred jOOQ. Večina sprememb DDL vpliva tudi na semantiko, ne le na sintakso. Zato je lahko koristno pogledati v prevedeno kodo, da vidite, na katero kodo bo (ali bi lahko) vplivalo povečanje vaše baze podatkov.

Edina resnica

Ne glede na to, katero tehnologijo uporabljate, vedno obstaja en model, ki je edini vir resnice za nek podsistem - ali pa bi si morali vsaj prizadevati za to in se izogniti takšni zmedi podjetij, kjer je "resnica" povsod in nikjer hkrati . Vse bi lahko bilo veliko bolj preprosto. Če samo izmenjujete datoteke XML z drugim sistemom, uporabite samo XSD. Oglejte si metamodel INFORMATION_SCHEMA iz jOOQ v obliki XML:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • XSD se dobro razume
  • XSD zelo dobro tokenizira vsebino XML in omogoča preverjanje v vseh jezikih strank
  • XSD ima dobro različico in ima napredno združljivost za nazaj
  • XSD je mogoče prevesti v kodo Java s pomočjo XJC

Zadnja točka je pomembna. Ko komuniciramo z zunanjim sistemom z uporabo sporočil XML, želimo biti prepričani, da so naša sporočila veljavna. To je zelo enostavno doseči z uporabo JAXB, XJC in XSD. Bila bi čista norost misliti, da bi jih lahko s pristopom oblikovanja "najprej Java", kjer svoja sporočila naredimo kot objekte Java, nekako skladno preslikali v XML in poslali v drug sistem za uporabo. XML, ustvarjen na ta način, bi bil zelo slabe kakovosti, nedokumentiran in bi ga bilo težko razviti. Če bi za tak vmesnik obstajal dogovor o ravni storitev (SLA), bi ga takoj zajebali.

Iskreno povedano, to se ves čas dogaja z API-ji JSON, a to je že druga zgodba, naslednjič se bom skregala ...

Baze podatkov: to je ista stvar

Ko delate z bazami podatkov, ugotovite, da so si v osnovi vse podobne. Baza je lastnik svojih podatkov in mora upravljati shemo. Vse spremembe sheme je treba implementirati neposredno v DDL, tako da je posodobljen en sam vir resnice.

Ko pride do posodobitve vira, morajo vsi odjemalci prav tako posodobiti svoje kopije modela. Nekatere odjemalce je mogoče napisati v Javi z uporabo jOOQ in Hibernate ali JDBC (ali obojega). Drugi odjemalci so lahko napisani v Perlu (želimo jim le veliko sreče), drugi pa v C#. Ni važno. Glavni model je v bazi podatkov. Modeli, ustvarjeni z uporabo ORM, so običajno slabe kakovosti, slabo dokumentirani in jih je težko razviti.

Zato ne delajte napak. Ne delajte napak že na samem začetku. Delo iz podatkovne baze. Zgradite cevovod za uvajanje, ki ga je mogoče avtomatizirati. Omogočite generatorje kode, da boste lahko kopirali svoj model baze podatkov in ga prenesli na odjemalce. In nehajte skrbeti za generatorje kod. Dobri so. Z njimi boste postali bolj produktivni. Samo nekaj časa morate porabiti za njihovo nastavitev od samega začetka - nato pa vas čakajo leta povečane produktivnosti, ki bodo sestavljala zgodovino vašega projekta.

Ne zahvaljuj se mi še, kasneje.

pojasnilo

Da bo jasno: ta članek nikakor ne zagovarja, da morate celoten sistem (tj. domeno, poslovno logiko itd. itd.) prilagoditi vašemu modelu baze podatkov. V tem članku želim povedati, da mora koda odjemalca, ki sodeluje z bazo podatkov, delovati na podlagi modela baze podatkov, tako da sama ne reproducira modela baze podatkov v statusu "prvega razreda". Ta logika se običajno nahaja na ravni dostopa do podatkov na vašem odjemalcu.

V dvonivojskih arhitekturah, ki so ponekod še ohranjene, je lahko tak sistemski model edini možen. Vendar se mi zdi, da je v večini sistemov plast dostopa do podatkov "podsistem", ki zajema model baze podatkov.

Izjeme

Pri vsakem pravilu so izjeme in rekel sem že, da je pristop generiranja najprej baze podatkov in izvorne kode lahko včasih neprimeren. Tukaj je nekaj takih izjem (verjetno so še druge):

  • Ko je shema neznana in jo je treba odkriti. Ste na primer ponudnik orodja, ki uporabnikom pomaga krmariti po katerem koli diagramu. Uf. Tukaj ni generiranja kode. Vendar je baza podatkov na prvem mestu.
  • Ko je treba vezje ustvariti sproti, da se reši neka težava. Ta primer se zdi kot nekoliko domiselna različica vzorca vrednost atributa entitete, tj. v resnici nimate jasno definirane sheme. V tem primeru pogosto sploh ne morete biti prepričani, da vam bo RDBMS ustrezal.

Izjeme so po naravi izjemne. V večini primerov, ki vključujejo uporabo RDBMS, je shema znana vnaprej, se nahaja znotraj RDBMS in je edini vir "resnice", vsi odjemalci pa morajo pridobiti kopije, ki izhajajo iz nje. V idealnem primeru morate uporabiti generator kode.

Vir: www.habr.com

Dodaj komentar