Pravda najprv, alebo prečo je potrebné navrhnúť systém na základe štruktúry databázy

Čau Habr!

Pokračujeme v skúmaní témy Jáva и jara to aj na úrovni databázy. Dnes vás pozývame, aby ste si prečítali, prečo by pri navrhovaní veľkých aplikácií mala byť rozhodujúca štruktúra databázy a nie kód Java, ako sa to robí a aké výnimky z tohto pravidla existujú.

V tomto dosť neskorom článku vysvetlím, prečo si myslím, že takmer vo všetkých prípadoch by mal byť dátový model v aplikácii navrhnutý skôr „z databázy“ než „z možností Java“ (alebo akéhokoľvek klientskeho jazyka, ktorý používate pracujúci s). Keď si vyberiete druhý prístup, pripravujete sa na dlhú cestu bolesti a utrpenia, keď váš projekt začne rásť.

Článok bol napísaný na základe jedna otázka, uvedené na Stack Overflow.

Zaujímavé diskusie na reddite v sekciách /r/java и /r/programovanie.

Generovanie kódu

Ako ma prekvapilo, že existuje taký malý segment používateľov, ktorí sa zoznámili s jOOQ a sú pobúrení skutočnosťou, že jOOQ sa pri svojej činnosti vážne spolieha na generovanie zdrojového kódu. Nikto vám nebráni používať jOOQ tak, ako uznáte za vhodné, ani vás nenúti používať generovanie kódu. Ale štandardný (ako je popísaný v príručke) spôsob práce s jOOQ je, že začnete so (starou) databázovou schémou, spätne ju analyzujete pomocou generátora kódu jOOQ, čím získate sadu tried reprezentujúcich vaše tabuľky, a potom napíšete typ -bezpečné dotazy na tieto tabuľky:

	for (Record2<String, String> record : DSL.using(configuration)
//   ^^^^^^^^^^^^^^^^^^^^^^^ Информация о типах выведена на 
//   основании сгенерированного кода, на который ссылается приведенное
// ниже условие SELECT 
 
       .select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
//           vvvvv ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^ сгенерированные имена
       .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ď potom Migrácia databázy Flyway, ktorú je možné vykonať aj manuálne alebo automaticky.

Generovanie zdrojového kódu

S týmito prístupmi ku generovaniu kódu – manuálne a automatické – sú spojené rôzne filozofie, výhody a nevýhody, ktoré v tomto článku nebudem podrobne rozoberať. Vo všeobecnosti však celý zmysel vygenerovaného kódu spočíva v tom, že nám umožňuje reprodukovať v jazyku Java tú „pravdu“, ktorú považujeme za samozrejmosť, či už v našom systéme alebo mimo neho. V istom zmysle to je to, čo kompilátory robia, keď generujú bajtový kód, strojový kód alebo inú formu zdrojového kódu – získame reprezentáciu našej „pravdy“ v inom jazyku, bez ohľadu na konkrétne dôvody.

Existuje veľa takýchto generátorov kódu. Napríklad, XJC dokáže generovať kód Java na základe súborov XSD alebo WSDL. Princíp je vždy rovnaký:

  • Existuje určitá pravda (vnútorná alebo vonkajšia) - napríklad špecifikácia, dátový model atď.
  • Potrebujeme lokálnu reprezentáciu tejto pravdy v našom programovacom jazyku.

Okrem toho je takmer vždy vhodné vytvoriť takúto reprezentáciu, aby sa predišlo nadbytočnosti.

Poskytovatelia typov a spracovanie anotácií

Poznámka: Ďalším, modernejším a špecifickejším prístupom ku generovaniu kódu pre jOOQ je použitie poskytovateľov typov, ako sú implementované v F#. V tomto prípade kód generuje kompilátor, vlastne vo fáze kompilácie. V zásade takýto kód v zdrojovej forme neexistuje. Java má podobné, aj keď nie také elegantné nástroje – anotačné procesory, napr. Lombok.

V istom zmysle sa tu dejú rovnaké veci ako v prvom prípade, s výnimkou:

  • Nevidíte vygenerovaný kód (možno sa táto situácia niekomu zdá menej odpudivá?)
  • Musíte zabezpečiť, aby bolo možné poskytnúť typy, to znamená, že „pravda“ musí byť vždy k dispozícii. To je jednoduché v prípade Lomboku, ktorý anotuje „pravdu“. Trochu komplikovanejšie je to s databázovými modelmi, ktoré závisia od neustále dostupného živého pripojenia.

Aký je problém s generovaním kódu?

Okrem záludnej otázky, ako čo najlepšie spustiť generovanie kódu – manuálne alebo automaticky, musíme spomenúť aj to, že existujú ľudia, ktorí veria, že generovanie kódu vôbec nie je potrebné. Zdôvodnenie tohto pohľadu, s ktorým som sa stretával najčastejšie, je, že je potom ťažké založiť build pipeline. Áno, je to naozaj ťažké. Vznikajú dodatočné náklady na infraštruktúru. Ak s konkrétnym produktom ešte len začínate (či už je to jOOQ, JAXB, alebo Hibernate atď.), nastavenie produkčného prostredia si vyžaduje čas, ktorý by ste radšej strávili učením sa samotného API, aby ste z neho mohli získať hodnotu. .

Ak sú náklady spojené s pochopením štruktúry generátora príliš vysoké, potom API skutočne odviedlo zlú prácu na použiteľnosti generátora kódu (a neskôr sa ukáže, že prispôsobenie používateľa v ňom je tiež ťažké). Použiteľnosť by mala byť najvyššou prioritou každého takéhoto rozhrania API. Ale to je len jeden argument proti generovaniu kódu. Inak je úplne manuálne písať lokálnu reprezentáciu vnútornej alebo vonkajšej pravdy.

Mnohí si povedia, že na to všetko nemajú čas. Bežia im termíny pre ich super produkt. Raz dáme do poriadku montážne dopravníky, budeme mať čas. Odpoviem im:

Pravda najprv, alebo prečo je potrebné navrhnúť systém na základe štruktúry databázy
Originál, Alan O'Rourke, skupina divákov

Ale v Hibernate/JPA je také ľahké písať kód Java.

Naozaj. Pre Hibernate a jeho používateľov je to požehnaním aj prekliatím. V režime dlhodobého spánku môžete jednoducho napísať niekoľko entít, napríklad:

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

A takmer všetko je pripravené. Teraz je na Hibernate, aby vygenerovala komplexné „podrobnosti“ o tom, ako presne bude táto entita definovaná 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 spustite aplikáciu. Naozaj skvelá príležitosť rýchlo začať a vyskúšať rôzne veci.

Dovoľte mi však. Klamal som.

  • Bude Hibernate skutočne presadzovať definíciu tohto pomenovaného primárneho kľúča?
  • Vytvorí režim dlhodobého spánku index v TITLE? – Viem určite, že to budeme potrebovať.
  • Umožní Hibernate presne identifikovať tento kľúč v špecifikácii identity?

Pravdepodobne nie. Ak vyvíjate svoj projekt od začiatku, vždy je vhodné jednoducho zahodiť starú databázu a vygenerovať novú, hneď ako pridáte potrebné anotácie. Entita Knihy teda v konečnom dôsledku nadobudne formu:

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

V pohode. Regenerovať. Aj v tomto prípade to bude na začiatku veľmi jednoduché.

Ale budete za to musieť zaplatiť neskôr

Skôr či neskôr budete musieť prejsť do výroby. Vtedy tento model prestane fungovať. Pretože:

Vo výrobe už nebude možné v prípade potreby zahodiť starú databázu a začať od nuly. Vaša databáza sa stane dedičstvom.

Odteraz a navždy budete musieť písať DDL migračné skripty, napríklad pomocou Flyway. Čo sa v tomto prípade stane s vašimi subjektmi? Môžete si ich prispôsobiť buď manuálne (a tým zdvojnásobiť svoju pracovnú záťaž), alebo môžete povedať Hibernate, aby vám ich vygenerovala (aká je pravdepodobnosť, že takto vygenerované budú spĺňať vaše očakávania?) V každom prípade prehrávate.

Takže akonáhle sa dostanete do výroby, budete potrebovať horúce záplaty. A treba ich veľmi rýchlo zaradiť do výroby. Keďže ste nepripravovali a neorganizovali hladký priebeh svojich migrácií do výroby, divoko všetko opravujete. A potom už nemáte čas robiť všetko správne. A kritizujete Hibernate, pretože je to vždy chyba niekoho iného, ​​len nie vás...

Namiesto toho sa veci mohli od začiatku robiť úplne inak. Dajte napríklad okrúhle kolesá na bicykel.

Najprv databáza

Skutočná „pravda“ vo vašej databázovej schéme a „suverenita“ nad ňou leží v databáze. Schéma je definovaná iba v samotnej databáze a nikde inde a každý klient má kópiu tejto schémy, takže má zmysel presadzovať súlad so schémou a jej integritu, robiť to priamo v databáze - kde sú informácie uložené.
Toto je stará, dokonca otrepaná múdrosť. Primárne a jedinečné kľúče sú dobré. Cudzie kľúče sú dobré. Kontrola obmedzení je dobrá. Tvrdenia - Dobre.

Navyše to nie je všetko. Napríklad pri použití Oracle by ste pravdepodobne chceli zadať:

  • V akom tabuľkovom priestore je váš stôl?
  • Aká je jeho hodnota PCTFREE?
  • Aká je veľkosť vyrovnávacej pamäte vo vašej sekvencii (za ID)

V malých systémoch to nemusí byť dôležité, ale nemusíte čakať, kým sa presuniete do oblasti veľkých dát – môžete začať ťažiť z optimalizácie úložiska od dodávateľa, ako sú tie spomenuté vyššie. Žiadny z ORM, ktorý som videl (vrátane jOOQ), neposkytuje prístup ku kompletnej sade možností DDL, ktoré by ste mohli chcieť použiť vo svojej databáze. ORM ponúkajú niektoré nástroje, ktoré vám pomôžu napísať DDL.

Ale na konci dňa je dobre navrhnutý obvod ručne napísaný v DDL. Akýkoľvek vygenerovaný DDL je len jeho aproximáciou.

A čo model klienta?

Ako je uvedené vyššie, na klientovi budete potrebovať kópiu schémy vašej databázy, pohľad klienta. Netreba pripomínať, že toto zobrazenie klienta musí byť synchronizované so skutočným modelom. Aký je najlepší spôsob, ako to dosiahnuť? Použitie generátora kódu.

Všetky databázy poskytujú svoje meta informácie prostredníctvom SQL. Tu je návod, ako získať všetky tabuľky z databázy 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 musíte brať do úvahy aj pohľady, materializované pohľady, tabuľkové funkcie) sa tiež vykonávajú volaním DatabaseMetaData.getTables() z JDBC alebo pomocou metamodulu jOOQ.

Z výsledkov takýchto dotazov je pomerne jednoduché vygenerovať akúkoľvek reprezentáciu vášho databázového modelu na strane klienta, bez ohľadu na to, akú technológiu na klientovi používate.

  • Ak používate JDBC alebo Spring, môžete vytvoriť sadu reťazcových konštánt
  • Ak používate JPA, môžete generovať samotné entity
  • Ak používate jOOQ, môžete vygenerovať meta-model jOOQ

V závislosti od toho, koľko funkcionality ponúka vaše klientske API (napr. jOOQ alebo JPA), môže byť vygenerovaný meta model skutočne bohatý a kompletný. Vezmime si napríklad možnosť implicitných spojení, predstavené v jOOQ 3.11, ktorý sa spolieha na vygenerované meta informácie o vzťahoch cudzích kľúčov, ktoré existujú medzi vašimi tabuľkami.

Teraz každý prírastok databázy automaticky aktualizuje kód klienta. Predstavte si napríklad:

ALTER TABLE book RENAME COLUMN title TO book_title;

Naozaj by ste chceli robiť túto prácu dvakrát? V žiadnom prípade. Jednoducho potvrďte DDL, spustite ho cez zostavovací kanál a získajte aktualizovanú entitu:

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

Alebo aktualizovaná trieda jOOQ. Väčšina zmien DDL ovplyvňuje aj sémantiku, nielen syntax. Preto môže byť užitočné pozrieť sa do skompilovaného kódu, aby ste videli, aký kód bude (alebo by mohol) ovplyvniť váš prírastok databázy.

Jediná pravda

Bez ohľadu na to, akú technológiu používate, vždy existuje jeden model, ktorý je jediným zdrojom pravdy pre nejaký subsystém – alebo by sme sa o to mali aspoň snažiť a vyhnúť sa takému podnikovému zmätku, kde je „pravda“ všade a nikde naraz. . Všetko by mohlo byť oveľa jednoduchšie. Ak si len vymieňate XML súbory s iným systémom, použite XSD. Pozrite si meta-model INFORMATION_SCHEMA z jOOQ vo forme XML:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • XSD je dobre pochopiteľné
  • XSD tokenizuje obsah XML veľmi dobre a umožňuje validáciu vo všetkých jazykoch klienta
  • XSD má dobrú verziu a má pokročilú spätnú kompatibilitu
  • XSD je možné preložiť do kódu Java pomocou XJC

Dôležitý je posledný bod. Pri komunikácii s externým systémom pomocou XML správ chceme mať istotu, že naše správy sú platné. To sa dá veľmi ľahko dosiahnuť pomocou JAXB, XJC a XSD. Bolo by číre šialenstvo myslieť si, že s dizajnovým prístupom „najskôr Java“, kde vytvárame naše správy ako objekty Java, že by mohli byť nejakým spôsobom koherentne mapované do XML a odoslané do iného systému na spotrebu. XML vygenerovaný týmto spôsobom by bol veľmi zlej kvality, nezdokumentovaný a ťažko by sa vyvíjal. Ak by pre takéto rozhranie existovala dohoda o úrovni služieb (SLA), okamžite by sme to pokazili.

Úprimne povedané, toto sa pri JSON API stáva stále, ale to je už iný príbeh, nabudúce sa pohádam...

Databázy: sú to isté

Pri práci s databázami si uvedomíte, že všetky sú v podstate podobné. Základňa vlastní svoje údaje a musí schému spravovať. Akékoľvek úpravy schémy musia byť implementované priamo v DDL, aby sa aktualizoval jediný zdroj pravdy.

Keď dôjde k aktualizácii zdroja, všetci klienti musia aktualizovať aj svoje kópie modelu. Niektorí klienti môžu byť napísaní v jazyku Java pomocou jOOQ a Hibernate alebo JDBC (alebo oboch). Ostatní klienti môžu byť napísaní v Perle (želáme im veľa šťastia), zatiaľ čo iní môžu byť napísaní v C#. To je jedno. Hlavný model je v databáze. Modely generované pomocou ORM sú zvyčajne nekvalitné, zle zdokumentované a ťažko sa vyvíjajú.

Takže nerobte chyby. Nerobte chyby hneď od začiatku. Pracujte z databázy. Vytvorte kanál nasadenia, ktorý možno automatizovať. Povoľte generátory kódu, aby ste uľahčili kopírovanie vášho databázového modelu a jeho výpis na klientov. A prestaňte sa starať o generátory kódu. Oni sú dobrí. S nimi budete produktívnejší. Stačí stráviť trochu času ich nastavovaním od úplného začiatku – a potom vás čakajú roky zvýšenej produktivity, ktoré budú tvoriť históriu vášho projektu.

Ešte mi neďakuj, neskôr.

objasnenie

Aby bolo jasné: Tento článok v žiadnom prípade neobhajuje, že musíte ohýbať celý systém (t. j. doménu, obchodnú logiku atď., atď.), aby vyhovoval vášmu databázovému modelu. V tomto článku hovorím, že kód klienta, ktorý interaguje s databázou, by mal fungovať na základe databázového modelu, aby sám nereprodukoval databázový model v stave „prvej triedy“. Táto logika sa zvyčajne nachádza na vrstve prístupu k údajom na vašom klientovi.

V dvojúrovňových architektúrach, ktoré sú na niektorých miestach ešte zachované, môže byť takýto systémový model jediným možným. Vo väčšine systémov sa mi však zdá, že vrstva prístupu k údajom je „subsystémom“, ktorý zapuzdruje databázový model.

výnimky

V každom pravidle existujú výnimky a už som povedal, že prístup k databáze a generovanie zdrojového kódu môže byť niekedy nevhodný. Tu je niekoľko takýchto výnimiek (pravdepodobne existujú aj iné):

  • Keď je schéma neznáma a je potrebné ju objaviť. Ste napríklad poskytovateľom nástroja, ktorý pomáha používateľom orientovať sa v akomkoľvek diagrame. Fuj. Tu sa negeneruje kód. Ale aj tak je na prvom mieste databáza.
  • Keď musí byť obvod generovaný za chodu, aby sa vyriešil nejaký problém. Tento príklad vyzerá ako trochu fantazijná verzia vzoru hodnota atribútu entity, t.j. v skutočnosti nemáte jasne definovanú schému. V tomto prípade si často ani nemôžete byť istí, že vám RDBMS bude vyhovovať.

Výnimky sú svojou povahou výnimočné. Vo väčšine prípadov, ktoré zahŕňajú použitie RDBMS, je schéma vopred známa, nachádza sa v RDBMS a je jediným zdrojom „pravdy“ a všetci klienti musia získať kópie z nej odvodené. V ideálnom prípade musíte použiť generátor kódu.

Zdroj: hab.com

Pridať komentár