Istina na prvom mjestu ili zašto sustav treba dizajnirati na temelju dizajna baze podataka

Hej Habr!

Nastavljamo s istraživanjem teme Java и Proljeće, uključujući na razini baze podataka. Danas vas pozivamo da pročitate zašto bi pri dizajniranju velikih aplikacija struktura baze podataka, a ne Java kod, trebala biti odlučujuća, kako se to radi i koje iznimke postoje od ovog pravila.

U ovom prilično kasnom članku, objasnit ću zašto vjerujem da bi u gotovo svim slučajevima podatkovni model u aplikaciji trebao biti dizajniran "iz baze podataka", a ne "iz mogućnosti Jave" (ili bilo kojeg klijentskog jezika koji koristite raditi sa). Prihvaćajući drugi pristup, pripremate se za dug put boli i patnje nakon što vaš projekt počne rasti.

Članak je napisan na temelju jedno pitanje, dano na Stack Overflowu.

Zanimljive rasprave o redditu u odjeljcima /r/java и /r/programiranje.

Generiranje koda

Kako sam bio iznenađen da postoji tako mali segment korisnika koji su, nakon što su upoznali jOOQ, ogorčeni činjenicom da se jOOQ ozbiljno oslanja na generiranje izvornog koda za rad. Nitko vas ne sprječava da koristite jOOQ kako vam odgovara, niti vas tjera da koristite generiranje koda. Ali zadani (kao što je opisano u priručniku) način rada s jOOQ-om je da počnete sa (naslijeđenom) shemom baze podataka, izvršite je obrnutim inženjeringom pomoću generatora koda jOOQ da dobijete skup klasa koje predstavljaju vaše tablice, a zatim napišete tip -sigurni upiti za ove tablice:

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

Kod se generira ili ručno izvan sklopa ili ručno na svakom sklopu. Na primjer, takva regeneracija može uslijediti odmah nakon toga Flyway migracija baze podataka, koja se također može obaviti ručno ili automatski.

Generiranje izvornog koda

Postoje različite filozofije, prednosti i nedostaci povezani s ovim pristupima generiranju koda - ručnim i automatskim - o kojima neću detaljno raspravljati u ovom članku. Ali, općenito, cijela poanta generiranog koda je da nam omogućuje da u Javi reproduciramo onu "istinu" koju uzimamo zdravo za gotovo, bilo unutar našeg sustava ili izvan njega. U određenom smislu, to je ono što prevoditelji rade kada generiraju bajt kod, strojni kod ili neki drugi oblik izvornog koda - dobivamo prikaz naše "istine" na drugom jeziku, bez obzira na specifične razloge.

Postoji mnogo takvih generatora kodova. Na primjer, XJC može generirati Java kod na temelju XSD ili WSDL datoteka. Princip je uvijek isti:

  • Postoji neka istina (unutarnja ili vanjska) - na primjer, specifikacija, podatkovni model itd.
  • Trebamo lokalni prikaz ove istine u našem programskom jeziku.

Štoviše, gotovo je uvijek preporučljivo generirati takav prikaz kako bi se izbjegla redundancija.

Pružatelji vrsta i obrada komentara

Napomena: drugi, moderniji i specifičniji pristup generiranju koda za jOOQ koristi pružatelje tipa, kako su implementirani u F#. U ovom slučaju, kod generira kompajler, zapravo u fazi kompilacije. U principu, takav kod ne postoji u izvornom obliku. Java ima slične, iako ne tako elegantne, alate - procesore komentara, na primjer, Lombok.

U određenom smislu, ovdje se događaju iste stvari kao u prvom slučaju, s izuzetkom:

  • Ne vidite generirani kod (možda je nekome ova situacija manje odbojna?)
  • Morate osigurati da se tipovi mogu pružiti, to jest, "true" mora uvijek biti dostupan. To je lako u slučaju Lomboka, koji označava "istinu". Malo je kompliciranije s modelima baza podataka koji ovise o stalno dostupnoj živoj vezi.

U čemu je problem s generiranjem koda?

Uz škakljivo pitanje kako najbolje pokrenuti generiranje koda - ručno ili automatski, moramo spomenuti i da ima ljudi koji smatraju da generiranje koda uopće nije potrebno. Opravdanje za ovo gledište, na koje sam najčešće nailazio, je da je onda teško postaviti build pipeline. Da, stvarno je teško. Nastaju dodatni troškovi infrastrukture. Ako tek počinjete s određenim proizvodom (bilo da se radi o jOOQ, ili JAXB, ili Hibernate, itd.), postavljanje proizvodnog okruženja zahtijeva vrijeme koje biste radije potrošili na učenje samog API-ja kako biste iz njega mogli izvući vrijednost .

Ako su troškovi povezani s razumijevanjem strukture generatora previsoki, onda je API doista loše odradio posao na upotrebljivosti generatora koda (a kasnije se ispostavlja da je i korisnička prilagodba u njemu složena). Upotrebljivost bi trebala biti najveći prioritet za svaki takav API. Ali ovo je samo jedan argument protiv generiranja koda. Inače, apsolutno je potpuno ručno napisati lokalni prikaz unutarnje ili vanjske istine.

Mnogi će reći da nemaju vremena za sve ovo. Istječu im rokovi za njihov Super proizvod. Jednom ćemo pospremiti montažne trake, imat ćemo vremena. Ja ću im odgovoriti:

Istina na prvom mjestu ili zašto sustav treba dizajnirati na temelju dizajna baze podataka
Original, Alan O'Rourke, Publika

Ali u Hibernate/JPA tako je lako napisati Java kod.

Stvarno. Za Hibernate i njegove korisnike ovo je i blagoslov i prokletstvo. U Hibernate možete jednostavno napisati nekoliko entiteta, ovako:

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

I gotovo je sve spremno. Sada je na Hibernateu da generira složene "detalje" o tome kako će točno ovaj entitet biti definiran u DDL-u vašeg SQL "dijalekta":

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

... i pokrenite aplikaciju. Stvarno super prilika za brzi početak i isprobavanje različitih stvari.

Ipak, molim vas, dopustite mi. Lagao sam.

  • Hoće li Hibernate stvarno primijeniti definiciju ovog imenovanog primarnog ključa?
  • Hoće li Hibernate stvoriti indeks u TITLE? – Znam sigurno da će nam trebati.
  • Hoće li Hibernate točno identificirati ovaj ključ u Specifikaciji identiteta?

Vjerojatno ne. Ako razvijate svoj projekt od nule, uvijek je zgodno jednostavno odbaciti staru bazu podataka i generirati novu čim dodate potrebne bilješke. Stoga će entitet knjige na kraju poprimiti oblik:

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

Cool. Regenerirati. Opet, u ovom slučaju bit će vrlo lako u početku.

Ali morat ćete to platiti kasnije

Prije ili kasnije morat ćete krenuti u proizvodnju. Tada će ovaj model prestati raditi. Jer:

U proizvodnji više neće biti moguće, ako bude potrebno, odbaciti staru bazu podataka i krenuti ispočetka. Vaša baza podataka postat će naslijeđe.

Od sada pa zauvijek morat ćeš pisati DDL migracijske skripte, na primjer, koristeći Flyway. Što će se dogoditi s vašim entitetima u ovom slučaju? Možete ih prilagoditi ručno (i time udvostručiti svoje radno opterećenje) ili možete reći Hibernateu da ih regenerira za vas (koliko je vjerojatno da će oni koji su generirani na ovaj način ispuniti vaša očekivanja?) U svakom slučaju, gubite.

Dakle, kada počnete s proizvodnjom, trebat će vam vruće zakrpe. I treba ih vrlo brzo staviti u proizvodnju. Budući da se niste pripremili i niste organizirali nesmetan cjevovod svojih migracija za proizvodnju, divljački sve krpate. A onda više nemate vremena učiniti sve kako treba. A ti kritiziraš Hibernate, jer uvijek je netko drugi kriv, samo ne ti...

Umjesto toga, stvari su se od samog početka mogle napraviti potpuno drugačije. Na primjer, stavite okrugle kotače na bicikl.

Prvo baza podataka

Prava "istina" u vašoj shemi baze podataka i "suverenitet" nad njom leži unutar baze podataka. Shema je definirana samo u samoj bazi podataka i nigdje drugdje, a svaki klijent ima kopiju ove sheme, tako da ima savršenog smisla nametnuti usklađenost sa shemom i njezin integritet, učiniti to upravo u bazi podataka - gdje su informacije pohranjeno.
Ovo je stara, čak otrcana mudrost. Primarni i jedinstveni ključevi su dobri. Strani ključevi su dobri. Provjera ograničenja je dobra. Tvrdnje - Dobro.

Štoviše, to nije sve. Na primjer, koristeći Oracle, vjerojatno biste željeli navesti:

  • U kojem se stolnom prostoru nalazi vaš stol?
  • Koja je njegova PCTFREE vrijednost?
  • Koja je veličina predmemorije u vašem nizu (iza ID-a)

Ovo možda nije važno u malim sustavima, ali ne morate čekati dok ne prijeđete u područje velikih podataka—možete početi koristiti prednosti optimizacija pohrane koje nudi dobavljač poput onih gore spomenutih mnogo ranije. Nijedan od ORM-ova koje sam vidio (uključujući jOOQ) ne pruža pristup punom skupu DDL opcija koje biste možda željeli koristiti u svojoj bazi podataka. ORM-ovi nude neke alate koji vam pomažu u pisanju DDL-a.

Ali na kraju dana, dobro dizajniran sklop je rukom napisan u DDL-u. Svaki generirani DDL samo je njegova aproksimacija.

Što je s modelom klijenta?

Kao što je gore spomenuto, na klijentu će vam trebati kopija vaše sheme baze podataka, prikaz klijenta. Nepotrebno je spominjati da ovaj pogled klijenta mora biti usklađen sa stvarnim modelom. Koji je najbolji način da se to postigne? Korištenje generatora koda.

Sve baze podataka pružaju svoje meta informacije putem SQL-a. Evo kako dobiti sve tablice iz vaše baze podataka u različitim SQL dijalektima:

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

Ovi upiti (ili slični, ovisno o tome morate li uzeti u obzir i poglede, materijalizirane poglede, funkcije s tabličnim vrijednostima) također se izvršavaju pozivom DatabaseMetaData.getTables() iz JDBC-a ili korištenjem meta-modula jOOQ.

Iz rezultata takvih upita relativno je lako generirati bilo koji prikaz vašeg modela baze podataka na strani klijenta, bez obzira koju tehnologiju koristite na klijentu.

  • Ako koristite JDBC ili Spring, možete stvoriti skup konstanti niza
  • Ako koristite JPA, možete generirati same entitete
  • Ako koristite jOOQ, možete generirati jOOQ meta-model

Ovisno o tome koliko funkcionalnosti nudi API vašeg klijenta (npr. jOOQ ili JPA), generirani meta model može biti stvarno bogat i potpun. Uzmimo, na primjer, mogućnost implicitnih spajanja, uveden u jOOQ 3.11, koji se oslanja na generirane meta informacije o odnosima stranih ključeva koji postoje između vaših tablica.

Sada će svako povećanje baze podataka automatski ažurirati kod klijenta. Zamislite na primjer:

ALTER TABLE book RENAME COLUMN title TO book_title;

Biste li doista htjeli dva puta raditi ovaj posao? Ni u kom slučaju. Jednostavno pokrenite DDL, pokrenite ga kroz svoj cjevovod izgradnje i preuzmite ažurirani entitet:

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

Ili ažuriranu klasu jOOQ. Većina DDL promjena također utječe na semantiku, ne samo na sintaksu. Stoga može biti korisno pogledati kompilirani kod kako biste vidjeli na koji će kod (ili bi mogao) utjecati povećanje vaše baze podataka.

Jedina istina

Bez obzira koju tehnologiju koristite, uvijek postoji jedan model koji je jedini izvor istine za neki podsustav - ili, u najmanju ruku, trebamo težiti tome i izbjeći takvu zabunu poduzeća, gdje je "istina" svugdje i nigdje odjednom . Sve bi moglo biti puno jednostavnije. Ako samo razmjenjujete XML datoteke s nekim drugim sustavom, samo koristite XSD. Pogledajte meta-model INFORMATION_SCHEMA iz jOOQ u XML obliku:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • XSD se dobro razumije
  • XSD jako dobro tokenizira XML sadržaj i omogućuje provjeru valjanosti na svim klijentskim jezicima
  • XSD je dobro verziran i ima naprednu kompatibilnost sa starijim verzijama
  • XSD se može prevesti u Java kod pomoću XJC

Posljednja točka je važna. Kada komuniciramo s vanjskim sustavom pomoću XML poruka, želimo biti sigurni da su naše poruke valjane. To je vrlo lako postići pomoću JAXB, XJC i XSD. Bilo bi čisto ludilo misliti da bi se, s "Java prva" pristupom dizajna gdje svoje poruke izrađujemo kao Java objekte, mogle nekako koherentno preslikati u XML i poslati drugom sustavu na korištenje. XML generiran na ovaj način bio bi vrlo loše kvalitete, nedokumentiran i težak za razvoj. Da postoji ugovor o razini usluge (SLA) za takvo sučelje, odmah bismo ga zeznuli.

Iskreno, to se stalno događa s JSON API-jima, ali to je druga priča, posvađat ću se sljedeći put...

Baze podataka: to je ista stvar

Kada radite s bazama podataka, shvatite da su sve one u osnovi slične. Baza posjeduje svoje podatke i mora upravljati shemom. Sve izmjene napravljene na shemi moraju se implementirati izravno u DDL tako da se ažurira jedinstveni izvor istine.

Kada dođe do ažuriranja izvora, svi klijenti također moraju ažurirati svoje kopije modela. Neki se klijenti mogu pisati u Javi koristeći jOOQ i Hibernate ili JDBC (ili oboje). Ostali klijenti mogu biti napisani u Perlu (samo im želimo puno sreće), dok drugi mogu biti napisani u C#. Nema veze. Glavni model je u bazi podataka. Modeli generirani pomoću ORM-ova obično su loše kvalitete, slabo dokumentirani i teški za razvoj.

Zato nemojte griješiti. Ne griješite od samog početka. Rad iz baze podataka. Izgradite cjevovod za implementaciju koji se može automatizirati. Omogućite generatore koda kako biste olakšali kopiranje vašeg modela baze podataka i njegovo izbacivanje na klijente. I prestanite brinuti o generatorima koda. Dobri su. S njima ćete postati produktivniji. Samo trebate potrošiti malo vremena na njihovo postavljanje od samog početka - a onda vas čekaju godine povećane produktivnosti, koje će činiti povijest vašeg projekta.

Nemoj mi još zahvaljivati, kasnije.

razjašnjenje

Da budemo jasni: ovaj članak ni na koji način ne zagovara da morate saviti cijeli sustav (tj. domenu, poslovnu logiku, itd., itd.) kako bi odgovarao vašem modelu baze podataka. Ono što govorim u ovom članku jest da kod klijenta koji je u interakciji s bazom podataka treba djelovati na temelju modela baze podataka, tako da on sam ne reproducira model baze podataka u statusu "prve klase". Ova se logika obično nalazi na sloju pristupa podacima na vašem klijentu.

U dvorazinskim arhitekturama, koje su još ponegdje sačuvane, takav model sustava može biti jedini mogući. Međutim, u većini sustava čini mi se da je sloj pristupa podacima "podsustav" koji sažima model baze podataka.

iznimke

Postoje iznimke od svakog pravila, a već sam rekao da pristup generiranju baze podataka i izvornog koda ponekad može biti neprikladno. Evo nekoliko takvih iznimaka (vjerojatno postoje i drugi):

  • Kada je shema nepoznata i treba je otkriti. Na primjer, vi ste pružatelj alata koji korisnicima pomaže u kretanju bilo kojim dijagramom. Uf. Ovdje nema generiranja koda. No ipak je baza podataka na prvom mjestu.
  • Kada se sklop mora generirati u hodu da bi se riješio neki problem. Ovaj primjer izgleda kao pomalo maštovita verzija uzorka vrijednost atributa entiteta, tj. nemate baš jasno definiranu shemu. U ovom slučaju često ne možete biti sigurni da će vam RDBMS odgovarati.

Iznimke su po prirodi izuzetne. U većini slučajeva koji uključuju korištenje RDBMS-a, shema je unaprijed poznata, nalazi se unutar RDBMS-a i jedini je izvor "istine", a svi klijenti moraju nabaviti kopije izvedene iz nje. U idealnom slučaju, trebate koristiti generator koda.

Izvor: www.habr.com

Dodajte komentar