Istina prije svega, ili zašto sistem treba biti dizajniran na osnovu uređaja baze podataka

Hej Habr!

Nastavljamo da istražujemo ovu temu Java и proljeće, uključujući i na nivou baze podataka. Danas vas pozivamo da pročitate zašto bi kod dizajniranja velikih aplikacija od presudnog značaja trebalo da bude struktura baze podataka, a ne Java kod, kako se to radi i koji izuzeci postoje od ovog pravila.

U ovom prilično kasnom članku objasnit ću zašto vjerujem da u gotovo svim slučajevima model podataka u aplikaciji treba biti dizajniran "iz baze podataka", a ne "iz mogućnosti Jave" (ili bilo kojeg klijentskog jezika koji ste raditi sa). Koristeći drugi pristup, postavljate se na dug put bola i patnje kada vaš projekat počne da raste.

Članak je napisan na osnovu jedno pitanje, dato na Stack Overflow.

Zanimljive rasprave o Redditu u sekcijama /r/java и /r/programiranje.

Generisanje koda

Koliko sam bio iznenađen da postoji tako mali segment korisnika koji su, nakon što su se upoznali sa jOOQ, ogorčeni činjenicom da se jOOQ ozbiljno oslanja na generisanje izvornog koda za rad. Niko vas ne sprečava da koristite jOOQ kako vam odgovara, niti vas tera da koristite generisanje koda. Ali podrazumevani (kao što je opisano u priručniku) način rada sa jOOQ je da počnete sa (nasleđenom) šemom baze podataka, obrnuti je inženjering koristeći generator koda jOOQ da biste na taj način dobili skup klasa koje predstavljaju vaše tabele, a zatim upišite tip -sigurni upiti ovim tabelama:

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

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

Generisanje izvornog koda

Postoje različite filozofije, prednosti i mane povezane s ovim pristupima generiranju koda - ručno i automatsko - o kojima neću detaljno raspravljati u ovom članku. Ali, generalno, cela poenta generisanog koda je u tome što nam omogućava da reprodukujemo u Javi tu „istinu“ koju uzimamo zdravo za gotovo, bilo unutar našeg sistema ili izvan njega. U određenom smislu, to je ono što kompajleri rade kada generišu bajtkod, mašinski kod ili neki drugi oblik izvornog koda - dobijamo reprezentaciju naše "istine" na drugom jeziku, bez obzira na specifične razloge.

Postoji mnogo takvih generatora koda. Na primjer, XJC može generirati Java kod baziran na XSD ili WSDL datotekama. Princip je uvek isti:

  • Postoji neka istina (interna ili eksterna) - na primjer, specifikacija, model podataka, itd.
  • Trebamo lokalno predstavljanje ove istine u našem programskom jeziku.

Štaviše, gotovo uvijek je preporučljivo generirati takvu reprezentaciju kako bi se izbjegla redundantnost.

Dobavljači tipova i obrada napomena

Napomena: drugi, moderniji i specifičniji pristup generiranju koda za jOOQ je korištenje provajdera tipova, 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 za napomene, na primjer, Lombok.

U određenom smislu, ovdje se dešavaju iste stvari kao i u prvom slučaju, sa izuzetkom:

  • Ne vidite generirani kod (možda se nekome ova situacija čini manje odbojnom?)
  • Morate osigurati da se tipovi mogu obezbijediti, to jest, "true" mora uvijek biti dostupno. To je lako u slučaju Lomboka, koji označava “istinu”. Malo je složenije s modelima baza podataka koji zavise od stalno dostupnoj živoj vezi.

Šta je problem sa generisanjem koda?

Pored škakljivog pitanja kako najbolje pokrenuti generiranje koda - ručno ili automatski, moramo napomenuti da postoje ljudi koji vjeruju da generiranje koda uopće nije potrebno. Opravdanje za ovu tačku gledišta, na koju sam najčešće nailazio, je da je tada teško postaviti cevovod za izgradnju. Da, zaista je teško. Nastaju dodatni infrastrukturni troškovi. Ako tek počinjete s određenim proizvodom (bilo da je to jOOQ, ili JAXB, ili hibernacija, itd.), postavljanje proizvodnog okruženja zahtijeva vrijeme koje biste radije potrošili na učenje samog API-ja kako biste mogli izvući vrijednost iz njega .

Ako su troškovi povezani sa razumijevanjem strukture generatora previsoki, tada je API zaista loše odradio upotrebljivost generatora koda (a kasnije se ispostavi da je i prilagođavanje korisnika u njemu složeno). Upotrebljivost bi trebala biti najviši prioritet za bilo koji takav API. Ali ovo je samo jedan argument protiv generiranja koda. Inače, potpuno je ručno pisati lokalni prikaz unutrašnje ili vanjske istine.

Mnogi će reći da nemaju vremena za sve ovo. Isteknu im rokovi za njihov Super proizvod. Jednog dana ćemo srediti montažne pokretne trake, imaćemo vremena. ja ću im odgovoriti:

Istina prije svega, ili zašto sistem treba biti dizajniran na osnovu uređaja baze podataka
Original, Alan O'Rourke, Grupa publike

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

Zaista. Za Hibernate i njegove korisnike ovo je i blagoslov i prokletstvo. U hibernaciji možete jednostavno napisati nekoliko entiteta, poput ovog:

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

I skoro sve je spremno. Sada je na Hibernaciji da generiše složene "detalje" o tome kako će tačno ovaj entitet biti definisan 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. Zaista super prilika da brzo počnete i isprobate različite stvari.

Međutim, dozvolite mi. Lagao sam.

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

Vjerovatno ne. Ako svoj projekat razvijate od nule, uvijek je zgodno jednostavno odbaciti staru bazu podataka i generirati novu čim dodate potrebne napomene. Dakle, entitet knjige će 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. Regenerirajte. Opet, u ovom slučaju će biti vrlo lako na početku.

Ali to ćete morati platiti kasnije

Prije ili kasnije morat ćete ući u proizvodnju. Tada će ovaj model prestati da radi. jer:

U produkciji više neće biti moguće, ako je potrebno, odbaciti staru bazu podataka i krenuti od nule. Vaša baza podataka će postati naslijeđe.

Od sada i zauvek moraćete da pišete DDL migracijske skripte, na primjer, koristeći Flyway. Šta će se dogoditi sa 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 Hibernate-u da ih regeneriše umjesto vas (koliko je vjerovatno da će oni generirani na ovaj način ispuniti vaša očekivanja?) U svakom slučaju, gubite.

Dakle, kada uđete u proizvodnju, trebat će vam vruće zakrpe. I treba ih vrlo brzo pustiti u proizvodnju. Pošto niste pripremili i niste organizirali glatki tok svojih migracija za proizvodnju, divlje sve krpite. I tada više nemate vremena da sve uradite kako treba. A ti kritikuješ Hibernate, jer je uvek neko drugi kriv, samo ne ti...

Umjesto toga, stvari su se mogle raditi potpuno drugačije od samog početka. Na primjer, stavite okrugle točkove na bicikl.

Prvo baza podataka

Prava "istina" u vašoj šemi 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 je savršeno logično nametnuti usklađenost sa shemom i njenim integritetom, da se to uradi upravo u bazi podataka - gdje su informacije pohranjeni.
Ovo je stara, čak i zajebana mudrost. Primarni i jedinstveni ključevi su dobri. Strani ključevi su dobri. Provjera ograničenja je dobra. Izjave - Dobro.

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

  • U kom se prostoru tablice nalazi vaša tablica?
  • Koja je njegova PCTFREE vrijednost?
  • Koja je veličina keša u vašem nizu (iza ID-a)

Ovo možda nije važno u malim sistemima, ali ne morate da čekate dok ne pređete u oblast velikih podataka – možete početi da dobijate koristi od optimizacija skladištenja koje obezbeđuje dobavljač, kao što su one gore pomenute 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 da napišete DDL.

Ali na kraju dana, dobro dizajnirano kolo je rukom napisano u DDL-u. Svaki generirani DDL je samo njegova aproksimacija.

Šta je sa modelom klijenta?

Kao što je gore spomenuto, na klijentu će vam trebati kopija vaše šeme baze podataka, klijentski pogled. 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 daju svoje meta informacije putem SQL-a. Evo kako da dobijete sve tabele iz vaše baze podataka na 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 da li morate uzeti u obzir i poglede, materijalizirane poglede, funkcije s vrijednostima tablice) također se izvršavaju pozivanjem DatabaseMetaData.getTables() iz JDBC-a, ili koristeći meta-modul jOOQ.

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

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

U zavisnosti od toga koliko funkcionalnosti nudi vaš klijentski API (npr. jOOQ ili JPA), generisani meta model može biti zaista bogat i potpun. Uzmimo, na primjer, mogućnost implicitnog spajanja, uvedeno u jOOQ 3.11, koji se oslanja na generirane meta informacije o odnosima stranih ključeva koji postoje između vaših tabela.

Sada će svaki porast baze podataka automatski ažurirati klijentski kod. Zamislite na primjer:

ALTER TABLE book RENAME COLUMN title TO book_title;

Da li biste zaista želeli da radite ovaj posao dvaput? Ni u kom slučaju. Jednostavno urezujte DDL, pokrenite ga kroz svoj cevovod za izgradnju 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žurirana klasa jOOQ. Većina DDL promjena također utiče na semantiku, a ne samo na sintaksu. Stoga može biti korisno pogledati u kompajliranom kodu da vidite na koji kod će (ili bi moglo) 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 podsistem - ili, u najmanju ruku, trebamo težiti tome i izbjegavati takvu zabunu preduzeća, gdje je "istina" svuda i nigdje odjednom . Sve bi moglo biti mnogo jednostavnije. Ako samo razmjenjujete XML datoteke sa nekim drugim sistemom, 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 razume
  • XSD tokenizira XML sadržaj vrlo dobro i omogućava validaciju na svim jezicima klijenta
  • XSD je dobro verzija i ima naprednu kompatibilnost unatrag
  • XSD se može prevesti u Java kod pomoću XJC

Poslednja tačka je važna. Kada komuniciramo sa eksternim sistemom koristeći XML poruke, želimo da budemo sigurni da su naše poruke validne. Ovo je vrlo lako postići korištenjem JAXB, XJC i XSD. Bilo bi ludo misliti da bi, sa pristupom dizajna "prvo na Javi", gdje svoje poruke pravimo kao Java objekte, one na neki način mogle biti koherentno mapirane u XML i poslane drugom sistemu za upotrebu. XML generisan na ovaj način bio bi veoma lošeg kvaliteta, nedokumentovan i težak za razvoj. Da postoji ugovor o nivou usluge (SLA) za takav interfejs, odmah bismo ga zeznuli.

Iskreno, ovo se stalno dešava sa JSON API-jem, ali to je druga priča, sledeći put ću se svađati...

Baze podataka: to su ista stvar

Kada radite sa bazama podataka, shvatite da su sve one u osnovi slične. Baza posjeduje svoje podatke i mora upravljati šemom. Sve modifikacije napravljene na šemi moraju se implementirati direktno u DDL tako da se ažurira jedan izvor istine.

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

Zato nemojte praviti greške. Ne pravite greške od samog početka. Radite iz baze podataka. Izgradite cevovod za implementaciju koji se može automatizovati. Omogućite generatore koda da biste olakšali kopiranje vašeg modela baze podataka i ispis na klijente. I prestanite da brinete o generatorima kodova. Oni su dobri. Uz njih ćete postati produktivniji. Samo trebate provesti malo vremena postavljajući ih od samog početka - a onda vas očekuju godine povećane produktivnosti koje će činiti povijest vašeg projekta.

Ne zahvaljuj mi još, kasnije.

Objašnjenje

Da budemo jasni: ovaj članak ni na koji način ne zagovara da morate saviti cijeli sistem (tj. domenu, poslovnu logiku, itd., itd.) kako bi se uklopio u vaš model baze podataka. Ono što govorim u ovom članku je da klijentski kod koji je u interakciji sa bazom podataka treba da deluje na osnovu modela baze podataka, tako da sam ne reprodukuje model baze podataka u "prvoklasnom" statusu. Ova logika se obično nalazi na sloju za pristup podacima na vašem klijentu.

U dvostepenim arhitekturama, koje su još ponegde sačuvane, ovakav sistemski model može biti jedini mogući. Međutim, u većini sistema sloj pristupa podacima mi se čini kao "podsistem" koji obuhvata model baze podataka.

Izuzeci

Postoje izuzeci od svakog pravila, a već sam rekao da pristup prve baze podataka i generisanja izvornog koda ponekad može biti neprikladan. Evo nekoliko takvih izuzetaka (vjerovatno ima i drugih):

  • Kada je shema nepoznata i treba je otkriti. Na primjer, vi ste dobavljač alata koji pomaže korisnicima da se kreću kroz bilo koji dijagram. Ugh. Ovdje nema generiranja koda. Ali ipak, baza podataka je na prvom mjestu.
  • Kada se krug mora generirati u hodu da bi se riješio neki problem. Ovaj primjer izgleda kao pomalo maštovita verzija uzorka vrijednost atributa entiteta, tj., zapravo nemate jasno definisanu šemu. U ovom slučaju često ne možete biti sigurni da će vam RDBMS odgovarati.

Izuzeci su po prirodi izuzetni. U većini slučajeva koji uključuju upotrebu RDBMS-a, shema je poznata unaprijed, nalazi se u RDBMS-u 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