Ahoj, Habr!
Naďalej skúmame tému a , vrátane úrovne databáz. Dnes vám ponúkame prečítať si, prečo by pri navrhovaní veľkých aplikácií mala mať práve štruktúra databázy, a nie Java kód, rozhodujúci význam, ako sa to robí a aké sú výnimky z tohto pravidla.
V tomto dosť oneskorenom článku vysvetlím, prečo si myslím, že takmer vo všetkých prípadoch by sa model údajov v aplikácii mal navrhovať "vychádzajúc z databázy", a nie "vychádzajúc z možností Java" (alebo iného klientskeho jazyka, s ktorým pracujete). Ak si vyberiete druhý prístup, vydávate sa na dlhú cestu bolesti a utrpenia, hneď ako váš projekt začne rásť.
Článok je napísaný na základe , položenej na Stack Overflow.
Zaujímavé diskusie na reddite v sekciách a .
Generovanie kódu
Aké veľké prekvapenie pre mňa bolo, že existuje taká malá vrstva používateľov, ktorí, keď sa oboznámili s jOOQ, sa rozhorčujú nad tým, že pri práci s jOOQ sa na generovanie zdrojového kódu spolieha vážne. Nikto vám nebráni používať jOOQ tak, ako si myslíte, a nenúti vás používať generovanie kódu. Ale predvolene (tak, ako je to popísané v príručke) sa práca s jOOQ uskutočňuje takto: začínate so (zdedenou) schémou databázy, vykonáte jej spätné inžinierstvo pomocou generátora kódu jOOQ, aby ste získali sadu tried reprezentujúcich vaše tabuľky, a potom píšete typovo bezpečné dotazy na tieto tabuľky:
for (Record2 record : DSL.using(configuration)
// ^^^^^^^^^^^^^^^^^^^^^^^ Informácie o typoch sú odvodené na
// základe vygenerovaného kódu, na ktorý sa odvoláva uvedené
// nižšie podmienka SELECT
.select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
// vvvvv ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ vygenerované mená
.from(ACTOR)
.orderBy(1, 2)) {
// ...
}Kód sa generuje buď ručne mimo zostavy, alebo ručne pri každej zostave. Napríklad taká regenerácia môže nasledovať hneď po .
Generovanie zdrojového kódu
S takými prístupmi k generovaniu kódu – ručnými a automatickými – sú spojené rôzne filozofie, výhody a nevýhody, ktoré neplánujem podrobne rozoberať v tomto článku. Ale vo všeobecnosti celá podstata generovaného kódu spočíva v tom, že umožňuje reprodukovať na Jave tú „pravdu“, ktorú považujeme za danú, či už v rámci nášho systému, alebo mimo neho. V istom zmysle to isté robia kompilátory, ktoré generujú bajtkód, strojový kód alebo akýsi iný typ kódu na základe zdrojových kódov – získavame predstavu našej „pravdy“ v inom jazyku, bez ohľadu na konkrétne dôvody.
Existuje množstvo takýchto generátorov kódu. Napríklad, Princíp je vždy rovnaký:
- Existuje akási pravda (vnútorná alebo vonkajšia) – napríklad špecifikácia, model dát atď.
- Potrebujeme lokálne zobrazenie tejto pravdy v našom programovacom jazyku.
A generovať také zobrazenie je takmer vždy rozumné – aby sa predišlo nadbytočnosti.
Poskytovatelia typov a spracovanie anotácií
Na poznámku: ďalší, modernejší a špecifickejší prístup k generovaniu kódu pre jOOQ je spojený s použitím poskytovateľov typov, V takom prípade kód generuje kompilátor vlastne na etapa kompilácie. V podobe zdrojových kódov taký kód v zásade neexistuje. V Jave existujú podobné, hoci nie tak elegantné nástroje – sú to procesory anotácií, napríklad .
V istom zmysle sa tu dejú tie isté veci ako v prvom prípade, s výnimkou:
- Nevidíte generovaný kód (možno sa niekomu táto situácia nezdá tak odstrašujúca?)
- Musíte zabezpečiť, že typy môžu byť poskytované, teda „pravda“ musí byť vždy dostupná. To je jednoduché v prípade Lombok, ktorý anotuje „pravdu“. O niečo zložitejšie je to s databázovými modelmi, ktorých funkcia závisí od neustále dostupného živého spojenia.
Aký je problém s generovaním kódu?
Okrem zložitých otázok o tom, akým spôsobom je lepšie spustiť generáciu kódu – ručne alebo automaticky, treba spomenúť aj to, že sú ľudia, ktorí si myslia, že generácia kódu vôbec nie je potrebná. Odôvodnenie tejto perspektívy, s ktorou som sa najčastejšie stretol, spočíva v tom, že je potom ťažké nastaviť zostavovací pipeline. Áno, naozaj to je zložité. Vznikajú dodatočné infraštruktúrne náklady. Ak len začínate pracovať s určitým produktom (či už je to jOOQ, alebo JAXB, alebo Hibernate, atď.), do nastavenia pracovného prostredia sa investuje čas, ktorý by ste radi venovali štúdiu samotného API, aby ste neskôr z neho mohli vyťažiť hodnotu.
Ak sú náklady spojené s porozumením fungovaniu generátora príliš vysoké – potom naozaj API zle pracovalo na používateľskej prívetivosti generátora kódu (a neskôr sa ukáže, že aj používateľské nastavenie v ňom je zložité). Pohodlie používania by malo byť najvyššou prioritou pre akékoľvek také API. Ale to je len jeden argument proti generácii kódu. V ostatnom prípade je absolútne potrebné písať lokálne zastúpenie vnútornej alebo vonkajšej pravdy úplne ručne.
Mnohí povedia, že nemajú čas sa tým všetkým zaoberať. Majú termíny na dodanie svojho Super- produktu. Niekedy neskôr vylepšíme zostavovacie pipeline, určite sa stihne. Odpoviem im:

,
Ale v Hibernate / JPA je tak jednoduché písať kód „pre Java“.
Naozaj. Pre Hibernate a jeho používateľov je to súčasne požehnanie a prekliatie. V Hibernate môžete jednoducho napísať niekoľko entít, takto:
@Entity
class Book {
@Id
int id;
String title;
}A takmer všetko je hotové. Teraz je úlohou Hibernate generovať zložité „detaily“ toho, ako sa táto entita presne určí v DDL vášho „dialektu“ 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);… a začíname spúšťať aplikáciu. Naozaj skvelá možnosť, ako sa rýchlo dostať do práce a skúšať rôzne veci.
Ale, dovoľte. Klamal som.
- A či Hibernate naozaj použije definíciu tohto pomenovaného primárneho kľúča?
- A či Hibernate vytvorí index v TITLE? – viem určite, že ho budeme potrebovať.
- A Hibernate naozaj urobí tento kľúč identifikujúci v Identity Specification?
Pravdepodobne nie. Ak vyvíjate svoj projekt od začiatku, je vždy pohodlné jednoducho sa zbaviť starej databázy a vygenerovať novú, hneď ako pridáte potrebné anotácie. Takto entita Book nakoniec získa tvar:
@Entity
@Table(name = "book", indexes = {
@Index(name = "i_book_title", columnList = "title")
})
class Book {
@Id
@GeneratedValue(strategy = IDENTITY)
int id;
String title;
}
Super. Vygenerovať znova. Opäť, v takom prípade bude na začiatku veľmi jednoduché.
Ale neskôr za to budete musieť zaplatiť.
Skôr či neskôr sa budete musieť presunúť do produkcie. A práve vtedy taký model prestane fungovať. Pretože:
V produkcii už nebude možné v prípade potreby zlikvidovať starú databázu a začať všetko odznova. Vaša databáza sa stane dedičnou.
Od teraz a navždy budete musieť písať . A čo sa v takom prípade stane s vašimi entitami? Budete ich môcť buď prispôsobiť ručne (a tak zdvojnásobíte množstvo práce), alebo prikážete Hibernate, aby ich znova vygeneroval pre vás (aká veľká je šanca, že takto vygenerovaný bude zodpovedať vašim očakávaniam?) Tak či onak, prehrávate.
Takto, keď sa presuniete do produkcie, budete potrebovať horúce opravy. A tie musíte rýchlo implementovať do produkcie. Keďže ste sa na to nepripravili a neorganizovali ste plynulý prenos vašich migrácií do produkcie, všetko to drasticky opravíte. A potom už nestihnete všetko spraviť správne. A obviňujete Hibernate, pretože vždy je niekto iný vinný, len nie vy...
Namiesto toho sa to od začiatku dalo robiť úplne inak. Napríklad dať na bicykel okrúhle kolesá.
Najprv databáza.
Skutočná "pravda" v schéme vašej databázy a "suverenita" nad ňou spočíva v databáze. Schéma je definovaná iba v samotnej databáze a nikde inde, a každý z klientov má kópiu tejto schémy, takže je úplne rozumné vynucovať dodržiavanie schémy a jej integrity priamo v databáze – tam, kde sú uložené informácie.
To je stará, dokonca aj opotrebovaná múdrosť. Primárne a unikátne kľúče sú dobré. Cudzie kľúče – dobré. Kontrola obmedzení – dobré. – dobré.
A to však nie je všetko. Napríklad pri použití Oracle pravdepodobne budete chcieť uviesť:
- V ktorom tabuľkovom priestore sa nachádza vaša tabuľka
- Aká je jej hodnota PCTFREE
- Aká je veľkosť cache vo vašej sekvencii (za identifikátorom)
Možno, že to všetko nie je dôležité v malých systémoch, ale nemusíte čakať na prechod do oblasti „veľkých dát“ — môžete začať čerpať výhody zo zdrojov optimalizácie u poskytovateľa skôr, ako sa dostanete do väčších systémov, ako sú spomínané vyššie. Žiadna z ORM, ktorú som videl (vrátane jOOQ), neposkytuje prístup k úplnej sade možností DDL, ktoré možno budete chcieť použiť vo svojej databáze. ORM ponúkajú niektoré nástroje, ktoré pomáhajú pri písaní DDL.
Ale nakoniec dobre navrhnutá schéma je napísaná ručne v DDL. Akýkoľvek generovaný DDL je len jej aproximáciou.
Čo na to klientský model?
Ako bolo spomenuté vyššie, na klientovi budete potrebovať kópiu schémy vašej databázy, klientské zobrazenie. Zbytočne uvádzať, že toto klientské zobrazenie musí byť synchronizované s reálnym modelom. Ako toho najlepšie dosiahnuť? Pomocou generátora kódu.
Všetky databázy poskytujú svoje meta informácie cez SQL. Takto môžete získať zo svojej databázy všetky tabuľky v rôznych dialektoch 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
Tieto dotazy (alebo podobné, v závislosti od toho, či sa musia brať do úvahy aj pohľady, materializované pohľady, funkcie s tabličným hodnotením) sa tiež vykonávajú prostredníctvom volania z JDBC, alebo pomocou meta-modulu jOOQ.
Z výsledkov takýchto dotazov je relatívne ľahké vygenerovať akékoľvek klientské zobrazenie modelu vašej databázy, bez ohľadu na to, aká technológia sa používajú na vašej strane.
- Ak používate JDBC alebo Spring, môžete vytvoriť množinu textových konštánt
- Ak používate JPA, môžete vygenerovať samotné entity
- Ak používate jOOQ, môžete vygenerovať meta-model jOOQ
V závislosti od toho, aký objem možností ponúka váš klientsky API (napr. jOOQ alebo JPA), môže byť vygenerovaný meta-model skutočne bohatý a kompletný. Zoberme si napríklad možnosť implicitných spojení, , ktorá sa zakladá na vygenerovaných metainformáciách o vzťahoch medzi vašimi tabuľkami na základe cudzích kľúčov.
Teraz každé zvýšenie databázy automaticky spôsobí aktualizáciu klientského kódu. Predstavte si napríklad:
ALTER TABLE book RENAME COLUMN title TO book_title;Naozaj by ste chceli robiť túto prácu dvakrát? Určite nie. Jednoducho uložíme DDL, preženeme ho cez váš build pipeline a získame aktualizovanú entitu:
@Entity
@Table(name = "book", indexes = {
\\ Zvažovali ste to?
@Index(name = "i_book_title", columnList = "book_title")
})
class Book {
@Id
@GeneratedValue(strategy = IDENTITY)
int id;
@Column("book_title")
String bookTitle;
}Alebo aktualizovanú triedu jOOQ. Väčšina zmien DDL sa odráža aj na semantike, nielen na syntaktike. Preto môže byť užitočné pozrieť sa na skompilovaný kód, ktorý kód bude (alebo môže byť) ovplyvnený zvýšením vašej databázy.
Jedna pravda
Bez ohľadu na to, akú technológiu používate, vždy existuje jeden model, ktorý je jediným zdrojom pravdy pre určitý subsystém – alebo, minimálne, mali by sme sa o to snažiť a vyhnúť sa takej podnikovej zmätku, kde je "pravda" okamžite všade a nikde. Všetko môže byť oveľa jednoduchšie. Ak iba vymieňate XML súbory s nejakým iným systémom, jednoducho používajte XSD. Pozrite sa na metamodel INFORMATION_SCHEMA z jOOQ vo formáte XML:
- XSD je dobre pochopiteľné
- XSD veľmi dobre označuje obsah XML a umožňuje validáciu vo všetkých klientskych jazykoch
- XSD je dobre verzionovateľné a má rozsiahlu spätnú kompatibilitu
- XSD môže byť preložené do kódu Java pomocou XJC
Posledný bod je dôležitý. Pri komunikácii s externým systémom pomocou XML správ chceme mať istotu o platnosti našich správ. Toho je veľmi ľahké dosiahnuť pomocou JAXB, XJC a XSD. Bolo by to úplné šialenstvo očakávať, že pri prístupe k návrhu „najprv Java“, kde vytvárame naše správy vo forme objektov Java, by sa dali nejako zrozumiteľne zobraziť na XML a zaslať na spracovanie do iného systému. XML generované takýmto spôsobom by malo veľmi nízku kvalitu, nebolo by zdokumentované a ťažko by sa vyvíjalo. Ak by existovala dohoda o úrovni kvality služieb (SLA) pre takýto rozhranie, okamžite by sme ju skazili.
Úprimne povedané, presne toto sa neustále deje s API na JSON, ale to je už iný príbeh, nabudúce sa o tom rozčúlim…
Databázy: je to to isté
Pri práci s databázami si uvedomujete, že všetky sú v zásade podobné. Databáza vlastní svoje dáta a mala by riadiť schému. Akékoľvek modifikácie v schéme by mali byť implementované priamo na DDL, aby sa aktualizoval jednotný zdroj pravdy.
Keď sa aktualizácia zdroja uskutoční, všetci klienti by mali tiež aktualizovať svoje kópie modelu. Niektorí klienti môžu byť napísaní v Jave pomocou jOOQ a Hibernate alebo JDBC (alebo všetky naraz). Iní klienti môžu byť napísaní v Perle (ostáva im popriať šťastie), traja – na C#. To nie je dôležité. Hlavný model je v databáze. Modely generované pomocou ORM sú zvyčajne nízkej kvality, zle zdokumentované a ťažko sa vyvíjajú.
Preto nerobte chyby. Od začiatku sa vyhnite chybám. Pracujte na základe databázy. Vytvorte taký nasadzovací proces, ktorý je možné automatizovať. Zahrňte generátory kódu, aby ste pohodlne skopírovali model vašej databázy a odovzdali ho na klientov. A prestaňte sa obávať generátorov kódu. Sú dobré. S nimi sa stanete produktívnejšími. Je potrebné len na začiatku venovať trochu času ich nastaveniu – a potom vás čakajú roky zvýšenej produktivity, ktoré sa v konečnom dôsledku stanú históriou vášho projektu.
Zatiaľ neďakujte, neskôr.
Vysvetlenie
Pre jasnosť: Tento článok určite nepropaguje, že celá vaša systém musí byť prispôsobená modelu vašej databázy (t.j. predmetná oblasť, obchodná logika atď.). V tomto článku hovorím o tom, že klientsky kód, ktorý interaguje s databázou, by sa mal orientovať na model databázy, aby v jeho vlastnom obsahu nebola reprodukovaná databázová schéma v statusu „prvej triedy“. Takáto logika je zvyčajne umiestnená na úrovni prístupu k dátam u vás na kliente.
V dvojúrovňových architektúrach, ktoré sa ešte stále niekde zachovali, môže byť takýto model systému jedinou možnosťou. Avšak vo väčšine systémov sa úroveň prístupu k dátam javí ako „pod systém“, ktorý enkapsuluje model databázy.
Výnimky
Z akéhokoľvek pravidla existujú výnimky, a už som povedal, že prístup s prioritou databázy a generovanie zdrojového kódu sa niekedy môže ukázať ako nevhodný. Tu je pár takýchto výnimiek (pravdepodobne existujú aj iné):
- Keď nie je známa schéma a je potrebné ju otvoriť. Napríklad, ste poskytovateľ nástroja, ktorý pomáha používateľom orientovať sa v akejkoľvek schéme. Uf. Bez generovania kódu tu. Ale aj tak – databáza je predovšetkým.
- Keď by sa schéma mala generovať na požiadanie na riešenie nejakého problému. Tento príklad sa javí ako trochu komplikovaná verzia vzoru , t.j., naozaj nemáte jasne definovanú schému. V takomto prípade často ani nie je možné byť si istý, že vám bude vyhovovať RDBMS.
Výnimky sú z podstaty výnimočné. Vo väčšine situácií súvisiacich s použitím RDBMS je schéma známa vopred, nachádza sa v RDBMS a je jediným zdrojom „pravdy“, a všetci klienti musia získať kópie odvodené od nej. Ideálne by sa pritom mal používať generátor kódu.
Zdroj: habr.com
