Resnica je na prvem mestu, ali zakaj je treba sistem načrtovati ob upoštevanju strukture baze podatkov

Hello, Habr!

Nadaljujemo z raziskovanjem teme Java in Spring, vključno na ravni podatkovnih baz. Danes vam priporočamo, da preberete, zakaj bi pri načrtovanju velikih aplikacij struktura podatkovne baze, ne pa Java koda, morala imeti odločilno vlogo, kako se to počne in kakšne izjeme obstajajo pri tem pravilu.

V tem precej zamujenem članku bom razložil, zakaj menim, da bi morala biti v skoraj vseh primerih model podatkov v aplikaciji načrtovana 'izhajajoč iz podatkovne baze', ne pa 'izhajajoč iz zmožnosti Java' (ali drugega odjemalčnega jezika, s katerim delate). Z izbiro drugega pristopa vstopite na dolgo pot bolečine in trpljenja, takoj ko se vaš projekt začne širiti.

Članek je napisan na podlagi enega vprašanja, postavljenega na Stack Overflow.

Zanimive razprave na redditu v oddelkih /r/java in /r/programming.

Generiranje kode

Koliko presenečen sem bil, da obstaja tako majhna plast uporabnikov, ki so ob spoznanju jOOQ razburjeni zaradi dejstva, da jOOQ pri delu resno zanaša na generiranje izvorne kode. Nihče vam ne preprečuje, da bi jOOQ uporabljali, kot menite, da je prav, in vas ne sili k uporabi generiranja kode. Vendar pa po privzetku (kot opisano v priročniku) delo z jOOQ poteka tako: začnete s (dedovano) shemo podatkovne baze, izvedete njeno obratno inženirstvo s pomočjo generatorja kode jOOQ, da pridobite nabor razredov, ki predstavljajo vaše tabele, nato pa pišete tipizirane poizvedbe v te tabele:

	for (Record2 record : DSL.using(configuration)
//   ^^^^^^^^^^^^^^^^^^^^^^^ Informacije o tipih so bile določene na 
//   podlagi generirane kode, na katero se nanaša navedena
//   spodaj pogoj SELECT 
 
       .select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
//           vvvvv ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^ generirana imena
       .from(ACTOR)
       .orderBy(1, 2)) {
    // ...
}

Koda se generira bodisi ročno zunaj zbirk bodisi ročno pri vsaki zbirki. Na primer, taka regeneracija lahko sledi takoj po migraciji podatkovne baze Flyway, ki jo je prav tako mogoče izvesti ročno ali samodejno..

Generiranje izvorne kode

S takimi pristopi k generaciji kode – ročnimi in avtomatskimi – so povezane različne filozofije, prednosti in slabosti, o katerih se v tem članku ne nameravam podrobno razpravljati. V splošnem je bistvo generiranega kode v tem, da nam omogoča, da na Javi rekonstruiramo to 'resnico', ki jo sprejemamo kot samoumevno, bodisi v okviru našega sistema bodisi izven njega. V nekem smislu to počnejo tudi prevajalniki, ki generirajo bajtno kodo, strojno kodo ali kakršno koli drugo vrsto kode na podlagi izvornih datotek – dobimo predstavitev naše 'resnice' v drugem jeziku, ne glede na konkretne razloge.

Obstaja mnogo takih generatorjev kode. Na primer, XJC lahko generira kode Java na osnovi datotek XSD ali WSDL. Načelo je vedno enako:

  • Obstaja neka resnica (notranja ali zunanja) – na primer, specifikacija, model podatkov itd.
  • Potrebujemo lokalno predstavitev te resnice v našem programskem jeziku.

Prav tako je skoraj vedno smiselno generirati to predstavitev – da se izognemo odvečnosti.

Povezovalci tipov in obdelava anotacij

Za opombo: še en, bolj sodoben in specifičen pristop k generaciji kode za jOOQ je povezan z uporabo povezovalcev tipov, v obliki, kot so implementirani v F#. V tem primeru generira kodo prevajalnik, in sicer v fazi prevajanja. Takšna koda načeloma ne obstaja v obliki izvornih datotek. V Javi obstajajo podobna orodja, čeprav ne tako elegantna – to so obdelovalniki anotacij, na primer, Lombok.

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

  • Ne vidite generirane kode (morda se zdi ta situacija nekaterim ne tako odvratna?)
  • Morate zagotoviti, da so tipi na voljo, torej, 'resnica' mora biti vedno dostopna. To je preprosto v primeru Lombok, ki anotira 'resnico'. Malce bolj zapleteno je z modeli baz podatkov, katerih delovanje je odvisno od nenehno dostopne žive povezave.

Kakšen je problem z generacijo kode?

Poleg premetne in subtilne debate o tem, kako najbolje zagnati generacijo kode – ročno ali avtomatsko, je treba omeniti, da so nekateri mnenja, da generacija kode sploh ni potrebna. Najpogostejši razlog za to stališče je, da je težko nastaviti proces zbiranja. Da, res je težko. Pojavijo se dodatne infrastrukturne stroške. Če šele začnete delati z določenim produktom (naj bo to jOOQ, JAXB, Hibernate itd.), to, da porabite čas za nastavitev delovnega okolja, pomeni, da ne morete študirati samega API-ja, da bi kasneje iz njega izluščili vrednost.

Če so stroški preveliki, ker se morate učiti o delovanju generatorja – potem res API ni dobro delal na uporabniški prijaznosti generatorja (in kmalu se izkaže, da je tudi uporabniška nastavitev v njem zapletena). Udobje uporabe bi moralo biti najvišja prioriteta za vsak tak API. Vendar je to le en argument proti generaciji kode. V drugih stvareh je povsem možno ročno napisati lokalno predstavitev notranje ali zunanje resnice.

Mnogi bodo rekli, da nimajo časa za to. Imajo roke za svoj Super-Produkt. Kdaj drugič se bomo posvetili procesom zbiranja, bomo imeli čas. Odgovorim jim:

Resnica je na prvem mestu, ali zakaj je treba sistem načrtovati ob upoštevanju strukture baze podatkov
Izvirnik, Alan O’Rourke, Audience Stack

Ampak v Hibernate / JPA je res enostavno pisati kodo 'pod Java'.

Res je. Za Hibernate in njegove uporabnike je to hkrati blagoslov in prekletstvo. V Hibernate lahko enostavno napišete nekaj entitet, na ta način:

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

In skoraj vse je pripravljeno. Zdaj je naloga Hibernate-a, da generira kompleksne 'detajle' o tem, kako natančno bo ta entiteta definirana v DDL vašega 'dialekta' 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čnemo poganjati aplikacijo. Res kul možnost, da hitro začnemo delati in preizkušamo različne stvari.

Vendar, dovolite, da se vrnem nazaj. Malce sem zavajal.

  • Ali Hibernate res uporabi definicijo tega poimenovanega primarnega ključa?
  • Ali Hibernate ustvari indeks v TITLE? – sem prepričan, da ga bomo potrebovali.
  • Ali Hibernate zagotovo naredi ta ključ identifikacijski v Identity Specifikaciji?

Verjetno ne. Če svoj projekt razvijate iz nič, je vedno priročno preprosto zavreči staro bazo podatkov in ustvariti novo, takoj ko dodate potrebne anotacije. Tako bo entiteta Book na koncu izgledala:

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

Super. Ponovno generiraj. Spet, v takem primeru bo na začetku zelo enostavno.

A vendar boste na koncu morali za to plačati.

Prej ali slej boste morali iti v produkcijo. Šele takrat ta model preneha delovati. Kajti:

V produkciji ne boste več mogli, če bo potrebno, zavreči staro bazo podatkov in začeti znova z ničle. Vaša baza podatkov se bo spremenila v dediščinsko.

Od zdaj naprej boste morali pisati DDL migracijske skripte, na primer, z uporabo Flyway.. Kaj se potem zgodi z vašimi entitetami? Lahko jih ročno prilagodite (in si tako podvojite obseg dela), ali pa naročite Hibernate, da jih ponovno generira za vas (kako velika je možnost, da bo tako generiran ustrezal vašim pričakovanjem?) Tako ali tako ste na slabem.

Tako boste, ko preidete v produkcijo, potrebovali hitre popravke. Te je treba hitro izvesti v produkciji. Ker se niste pripravili in organizirali gladke konvejerizacije vaših migracij za produkcijo, boste vse grobo popravili. In nato ne boste imeli časa, da bi to pravilno opravili. In obtožujete Hibernate, ker je vedno kriv kdo drug, samo vi ne...

Namesto tega bi lahko vse od začetka počeli povsem drugače. Na primer, namestite kroglice na kolo.

Sprva baza podatkov.

Prava 'resnica' v shemi vaše baze podatkov in 'suverenost' nad njo leži znotraj baze podatkov. Shema je določena le v sami bazi podatkov in nikjer drugje, in vsak od strank ima kopijo te sheme, zato je povsem smiselno uvesti spoštovanje sheme in njeno celovitost, to izvajati neposredno v bazi podatkov - tam, kjer se shranjujejo informacije.
To je stara, celo izpiljena modrost. Primarni in unikatni ključi so dobri. Zunanji ključi so dobri. Preverjanje omejitev je dobro. Navedbe so dobre.

In addition, this is not everything. For example, when using Oracle, you will likely want to specify:

  • In which tablespace your table resides
  • What is its PCTFREE value
  • What is the cache size in your sequence (after the identifier)

Perhaps all this isn't important in small systems, but you don't necessarily have to wait for the transition to 'big data' — you can start benefiting from the optimizations provided by the vendor much earlier, such as the ones mentioned above. None of the ORMs I've seen (including jOOQ) provide access to the full range of DDL options that you might want to use in your database. ORMs offer some tools that help write DDL.

But in the end, a well-designed schema is manually written in DDL. Any generated DDL is just an approximation of it.

What about the client model?

As mentioned above, on the client side, you will need a copy of your database schema, a client view. It's unnecessary to mention that this client view must be synchronized with the actual model. How can this best be achieved? By using a code generator.

All databases provide their metadata via SQL. Here’s how to obtain all tables from your database in various SQL dialects:

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

These queries (or similar ones, depending on whether views, materialized views, or table-valued functions also need to be considered) are also executed using the call DatabaseMetaData.getTables() from JDBC, or through the jOOQ meta-module.

From the results of such queries, it is relatively easy to generate any client view of your database model, regardless of what technology you are using on the client side.

  • If you are using JDBC or Spring, you can create a set of string constants
  • If you are using JPA, you can generate the entities yourself
  • If you are using jOOQ, you can generate the jOOQ meta-model

Glede na to, koliko možnosti ponuja vaš odjemalski API (npr. jOOQ ali JPA), lahko generirani meta model postane res bogat in celovit. Vzemimo na primer možnost implicitnih združitev, ki je bila uvedena v jOOQ 3.11, ki se opira na generirane meta informacije o odnosih zunanjih ključev, ki veljajo med vašimi tabelami.

Zdaj bo vsako povečanje baze podatkov samodejno privedlo do posodobitve odjemalske kode. Predstavljajte si na primer:

ALTER TABLE book RENAME COLUMN title TO book_title;

Ali res želite to delo opraviti dvakrat? Seveda ne. Preprosto zabeležimo DDL, ga preusmerimo skozi vaš gradbeni proces in dobimo posodobljeno entiteto:

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

  // Ste o tem razmišljali?
  @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 se odraža tudi na semantiki, ne samo na sintaksi. Zato je včasih priročno pogledati v skompilirano kodo, katera koda bo (ali bi lahko bila) prizadeta pri povečanju vaše baze podatkov.

Edina resnica

Ne glede na tehnologijo, ki jo uporabljate, vedno obstaja en model, ki je edini vir resnice za neko podskupino – ali, vsaj, stremimo k temu in se izogibamo takšni podjetniški zmedi, kjer je "resnica" naletela povsod in nikjer. Vse bi bilo lahko precej preprosto. Če samo izmenjujete datoteke XML z drugo sistemom, preprosto uporabite XSD. Oglejte si meta-model INFORMATION_SCHEMA iz jOOQ v XML obliki:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • XSD je dobro razumljiv
  • XSD zelo dobro označuje vsebino XML in omogoča validacijo v vseh odjemalskih jezikih
  • XSD dobro različicira in ima razvito obratno združljivost
  • XSD se lahko pretvori v Java kodo s pomočjo XJC

Zadnji punkt je pomemben. Pri komunikaciji z zunanjim sistemom z uporabo XML-sporočil želimo biti prepričani o veljavnosti naših sporočil. To je zelo enostavno doseči z uporabo JAXB, XJC in XSD. Bilo bi popolnoma noro pričakovati, da bi pri pristopu k oblikovanju „najprej Java“, kjer sporočila ustvarjamo kot Java objekte, bilo mogoče na kakršen koli smiseln način prevesti te v XML in jih poslati v drug sistem. XML, ustvarjen na ta način, bi bil zelo slabe kakovosti, brez dokumentacije in težko razširljiv. Če bi za takšno vmesnik obstajala dogovor o ravni storitev (SLA), bi ga takoj pokvarili.

Iskreno, prav to se nenehno dogaja z API-ji na JSON, vendar je to že druga zgodba, naslednjič se bom jezil...

Baze podatkov: to je isto

Ko delate z bazami podatkov, razumete, da so vse v bistvu podobne. Baza obvladuje svoje podatke in mora upravljati shemo. Vse spremembe, ki jih uvedete v shemo, morajo biti izvedene neposredno na DDL, da se posodobi enotni vir resnice.

Ko se posodobitev vira zgodi, morajo tudi vsi klienti posodobiti svoje kopije modela. Nekateri klienti so lahko napisani v Javi z uporabo jOOQ in Hibernate ali JDBC (ali vse naenkrat). Drugi klienti so lahko napisani v Perl (ostavlja jim srečo), tretji – v C#. To ni pomembno. Glavni model se nahaja v bazi podatkov. V modelih, generiranih z ORM, je običajno slaba kakovost, slabo dokumentirano in težko jih je razvijati.

Zato ne delajte napak. Od samega začetka ne delajte napak. Delajte na podlagi baze podatkov. Zgradite takšno infrastrukturo za razporejanje, ki jo je mogoče avtomatizirati. Vključite generatorje kode, da boste lahko enostavno kopirali model vaše baze podatkov in ga prenesli na kliente. In prenehajte skrbeti za generatorje kode. So dobri. S tem boste postali produktivnejši. Samo na začetku morate nameniti nekaj časa za njihovo nastavitev – in potem vas čakajo leta povečane produktivnosti, iz katerih se bo razvila zgodba vašega projekta.

Zaenkrat se ne zahvaljujte, kasneje.

Objašnjenje

Za jasnoću: Ovaj članak nikako ne propagira ideju da se kompletan sistem (tj. predmetna oblast, poslovna logika itd.) mora prilagoditi modelu vaše baze podataka. U ovom članku govorim o tome da klijentski kod koji komunicira sa bazom podataka treba delovati na osnovu modela baze podataka, kako se sam ne bi reprodukovao kao model baze podataka prvog reda. Ova logika obično se nalazi na nivou pristupa podacima na vašem klijentu.

U dvojnim arhitekturama, koje se još uvek ponegde zadržavaju, ovakav sistemski model može biti jedini moguć. Međutim, u većini sistema, nivo pristupa podacima izgleda kao 'podsystem', koji inkapsulira model baze podataka.

Izjeme

Svako pravilo ima svoje izuzetke, i već sam spomenuo da pristup s primarnošću baze podataka i generisanjem izvornog koda ponekad može biti neodgovarajući. Evo nekoliko takvih izuzetaka (verovatno će ih biti i više):

  • Kada je šema nepoznata, i treba je otvoriti. Na primer, vi ste pružatelj alata koji pomaže korisnicima da se snalaze u bilo kojoj šemi. Uf. Tu bez generisanja koda. Ali, opet – baza podataka je prvo.
  • Kada šema treba biti generisana u hodu za rešavanje određenog zadatka. Ovaj primer izgleda kao malo pretenciozna verzija obrasca entity attribute value, tj. vi zapravo nemate jasno definisanu šemu. U ovom slučaju često ne može biti sigurno da će vam odgovarati RDBMS.

Izuzeci po svojoj prirodi su izuzetni. U većini slučajeva vezanih za korišćenje RDBMS, šema je unapred poznata, nalazi se unutar RDBMS i predstavlja jedini izvor 'istine', dok svi klijenti moraju nabaviti kopije izvedene iz nje. Idealno bi bilo u ovom slučaju angažovati generator koda.

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster