Első az igazság, avagy miért kell a rendszert az adatbázis-struktúra alapján megtervezni

Szia Habr!

Folytatjuk a téma feltárását Jáva и Tavaszbeleértve az adatbázis szintjét is. Ma arról ajánlunk olvasnivalót, hogy a nagy alkalmazások tervezésekor miért az adatbázis-struktúra, és nem a Java kód legyen a döntő fontosságú, hogyan történik ez, és mik a kivételek e szabály alól.

Ebben a meglehetősen megkésett cikkben elmagyarázom, miért gondolom, hogy az alkalmazások adatmodelljét szinte minden esetben "az adatbázisból" kell megtervezni, nem pedig "a Java képességeiből" (vagy bármilyen ügyfélnyelven) dolgozni vele). A második megközelítés választásával a fájdalom és a szenvedés hosszú útjára lép, amint a projektje növekedni kezd.

alapján íródott a cikk egy kérdés, adott a Stack Overflow.

Érdekes beszélgetések a redditről szekciókban /r/java и /r/programozás.

Kódgenerálás

Mennyire meglep, hogy van egy ilyen kis felhasználói réteg, akik a jOOQ megismerése után nehezményezik, hogy a jOOQ komolyan a forráskód generálásától függ. Senki sem akadályoz meg abban, hogy a jOOQ-t úgy használd, ahogy jónak látod, és senki sem kényszerít arra, hogy kódgenerálást használj. De alapértelmezés szerint (a kézikönyvben leírtak szerint) a jOOQ a következőképpen működik: egy (örökölt) adatbázissémával kezdi, visszafejti a jOOQ kódgenerátorral, hogy megkapja a tábláit reprezentáló osztálykészletet, majd írja be a következőt: biztonságos lekérdezések ezekhez a táblákhoz:

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

A kód vagy manuálisan generálódik a builden kívül, vagy manuálisan minden buildben. Például egy ilyen regeneráció közvetlenül ezután következhet Flyway adatbázis migráció, amely manuálisan vagy automatikusan is elvégezhető.

Forráskód generálása

Különféle filozófiák, előnyök és hátrányok kapcsolódnak a kódgenerálás ezen megközelítéseihez - manuálisan és automatikus -, amelyeket ebben a cikkben nem fogok részletesen tárgyalni. De általánosságban a generált kód lényege az, hogy lehetővé teszi az általunk természetesnek tartott „igazság” reprodukálását Java nyelven, akár a rendszerünkön belül, akár azon kívül. Bizonyos értelemben a bájtkódot, gépi kódot vagy más típusú kódot forráskódból generáló fordítók ugyanezt teszik – konkrét okoktól függetlenül egy másik nyelven kapjuk meg az „igazságunk” reprezentációját.

Sok ilyen kódgenerátor létezik. Például, Az XJC képes Java kódot generálni XSD vagy WSDL fájlok alapján. Az elv mindig ugyanaz:

  • Van némi igazság (belső vagy külső) - például egy specifikáció, egy adatmodell stb.
  • Szükségünk van ennek az igazságnak a helyi megjelenítésére a programozási nyelvünkben.

Sőt, szinte mindig ajánlatos ilyen reprezentációt generálni - a redundancia elkerülése érdekében.

Típusszolgáltatók és megjegyzések feldolgozása

Megjegyzés: A jOOQ kódgenerálásának egy másik, modernebb és specifikusabb megközelítése típusszolgáltatók használatát foglalja magában, ahogyan F#-ban vannak megvalósítva. Ebben az esetben a kódot a fordító állítja elő, valójában a fordítási szakaszban. Elvileg ilyen kód nem létezik forráskódok formájában. A Java-ban vannak hasonló, bár nem olyan elegáns eszközök - ezek például a megjegyzésfeldolgozók, Lombok.

Bizonyos értelemben ugyanazok a dolgok történnek itt, mint az első esetben, kivéve:

  • Nem látja a generált kódot (lehet, hogy ez a helyzet valakinek nem tűnik olyan visszataszítónak?)
  • Biztosítania kell a típusok megadását, vagyis a "true"-nak mindig elérhetőnek kell lennie. Ez könnyű az "igazságot" jegyző Lombok esetében. Ez egy kicsit nehezebb azokkal az adatbázismodellekkel, amelyek mindig elérhető élő kapcsolattól függenek.

Mi a probléma a kód generálásával?

A trükkös kérdés mellett, hogy hogyan érdemes - manuálisan vagy automatikusan - elindítani a kódgenerálást, meg kell említenem, hogy vannak, akik úgy vélik, hogy a kódgenerálásra egyáltalán nincs szükség. Ennek az álláspontnak az indoklása, amellyel a leggyakrabban találkoztam, hogy ilyenkor nehéz felállítani az építési csővezetéket. Igen, nagyon nehéz. További infrastrukturális költségek merülnek fel. Ha még csak most kezdi használni egy adott terméket (legyen az jOOQ, JAXB, vagy Hibernate stb.), akkor időbe telik egy munkapad felállítása, amelyet magának az API-nak a tanulására szeretne fordítani, hogy értéket hozzon ki belőle. .

Ha a generátor eszközének megértésével kapcsolatos költségek túl magasak, akkor az API valóban rosszul dolgozott a kódgenerátor használhatóságán (és a jövőben kiderül, hogy a testreszabás is nehézkes). A használhatóság legyen a legmagasabb prioritás minden ilyen API esetében. De ez csak egy érv a kódgenerálás ellen. Ellenkező esetben írja le teljesen kézzel a belső vagy külső igazság helyi reprezentációját.

Sokan azt mondják, hogy nincs idejük mindezt megtenni. Lejárt a határidő a szupertermékükre. Valamikor később átfésüljük az összeszerelő szállítószalagokat, lesz időnk. Válaszolok nekik:

Első az igazság, avagy miért kell a rendszert az adatbázis-struktúra alapján megtervezni
Eredeti, Alan O'Rourke, Audience Stack

De a Hibernate/JPA-ban olyan könnyű kódot írni "Java nyelven".

Igazán. A Hibernate és felhasználói számára ez egyszerre áldás és átok. Hibernált állapotban egyszerűen írhat néhány entitást, például:

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

És szinte minden készen áll. Most a Hibernate feladata, hogy összetett "részleteket" generáljon arról, hogyan lesz pontosan meghatározva ez az entitás az SQL "dialektusa" DDL-jében:

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

... és indítsa el az alkalmazás futtatását. Nagyon klassz funkció, amellyel gyorsan elindulhat, és különféle dolgokat próbálhat ki.

Azonban engedje meg. Hazudtam.

  • A Hibernate valóban érvényesíteni fogja ennek a megnevezett elsődleges kulcsnak a meghatározását?
  • A Hibernate létrehoz egy indexet a következőn: TITLE? Biztosan tudom, hogy szükségünk van rá.
  • A Hibernate ezt a kulcsot identitáskulccsá teszi az Identity Specifikációban?

Valószínűleg nem. Ha a projektet a semmiből fejleszti, mindig kényelmes, ha egyszerűen eldobja a régi adatbázist, és azonnal létrehoz egy újat, amint hozzáadja a szükséges megjegyzéseket. Tehát a Könyv entitás végül a következő formában lesz:

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

Menő. Regenerátum. Ebben az esetben is nagyon könnyű lesz az elején.

De később fizetni kell érte.

Előbb-utóbb be kell szállnia a gyártásba. Ekkor a modell leáll. Mert:

A termelésben már nem lesz lehetőség szükség esetén a régi adatbázis eldobására, és mindent elölről kezdeni. Adatbázisa örökölt lesz.

Mostantól mindörökké írnod ​​kell DDL migrációs szkriptek, például a Flyway használatával. És mi lesz ebben az esetben az entitásaiddal? Személyre szabhatja őket manuálisan (és megduplázhatja a munkaterhelést), vagy megkérheti a Hibernate szolgáltatást, hogy újragenerálja őket (mennyire valószínű, hogy az így előállított példány megfelel az elvárásoknak?). Bármelyik esetben veszít.

Így, amint elkezdi a termelést, azonnali javításokra lesz szüksége. És nagyon gyorsan gyártásba kell hozni őket. Mivel nem készítette elő és nem szervezte meg az éles migráció zökkenőmentes folyamatát, vadul foltozgat. És akkor nincs időd mindent jól csinálni. És szidja a Hibernate-et, mert mindig bárki hibás, de nem te...

Ehelyett a kezdetektől fogva mindent teljesen másképp lehetett volna csinálni. Például tegyen kerek kerekeket egy kerékpárra.

Először az adatbázis

Az adatbázissémában az igazi "igazság" és a felette fennálló "szuverenitás" az adatbázisban rejlik. A séma csak magában az adatbázisban van definiálva, és sehol máshol, és minden kliensnek van egy példánya ebből a sémából, így teljesen logikus a séma betartását és integritását kikényszeríteni, ezt közvetlenül az adatbázisban megtenni - ahol a információkat tárolnak.
Ez régi, sőt elcsépelt bölcsesség. Az elsődleges és egyedi kulcsok jók. Az idegen kulcsok jók. A kényszerellenőrzés jó. Állítások - Bírság.

És ez még nem minden. Például az Oracle használatával valószínűleg meg szeretné adni:

  • Milyen asztaltérben van az asztalod?
  • Mi a PCTFREE értéke?
  • Mekkora a gyorsítótár mérete a sorozatodban (az azonosító mögött)

Lehet, hogy mindez kis rendszerekben nem számít, de nem kell megvárni a „big data” birodalmába való átállást – már jóval korábban profitálhat a gyártók által biztosított tárhelyoptimalizálásokból, mint amilyeneket fentebb említettünk. Az általam látott ORM-ek egyike sem (beleértve a jOOQ-t is) nem biztosít hozzáférést a DDL-beállítások teljes készletéhez, amelyeket esetleg használni szeretne az adatbázisában. Az ORM-ek néhány eszközt kínálnak a DDL írásához.

De a nap végén egy jól megtervezett sémát kézzel írnak DDL-ben. Bármelyik generált DDL annak csak közelítése.

Mi a helyzet a kliens modellel?

Ahogy fentebb említettük, az ügyfélen szüksége lesz az adatbázisséma másolatára, az ügyfélnézetre. Mondanom sem kell, ennek az ügyfélnézetnek szinkronban kell lennie a valós modellel. Mi a legjobb módja ennek elérésére? Kódgenerátorral.

Minden adatbázis SQL-en keresztül biztosítja metainformációit. A következőképpen kérheti le az adatbázisból az összes különböző SQL dialektusú táblát:

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

Ezeket a lekérdezéseket (vagy hasonlókat, attól függően, hogy nézeteket, materializált nézeteket, tábla értékű függvényeket is figyelembe kell-e venni) szintén a hívás hajtja végre. DatabaseMetaData.getTables() JDBC-ből, vagy a jOOQ meta-modul használatával.

Az ilyen lekérdezések eredményeiből viszonylag könnyen előállítható az adatbázismodell bármely kliensoldali reprezentációja, függetlenül attól, hogy milyen technológiát használ az ügyfélen.

  • Ha JDBC-t vagy Spring-et használ, létrehozhat karakterlánc-konstansokat
  • Ha JPA-t használ, akkor maguk az entitások generálhatók
  • Ha jOOQ-t használ, létrehozhat jOOQ metamodellt

Attól függően, hogy a kliens API mennyi képességet kínál (pl. jOOQ vagy JPA), a generált metamodell valóban gazdag és teljes lehet. Vegyük például az implicit összekapcsolás lehetőségét, bevezetve a jOOQ 3.11-ben, amely a táblák közötti idegen kulcs-kapcsolatokról generált metainformációkra támaszkodik.

Mostantól minden adatbázis-növekmény automatikusan frissíti az ügyfélkódot. Képzeld el például:

ALTER TABLE book RENAME COLUMN title TO book_title;

Tényleg szeretné ezt a munkát kétszer elvégezni? Semmilyen esetben sem. Csak véglegesítjük a DDL-t, futtatjuk az összeállítási folyamaton keresztül, és megkapjuk a frissített entitást:

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

Vagy a frissített jOOQ osztály. A legtöbb DDL-módosítás a szemantikára is hatással van, nem csak a szintaxisra. Ezért kényelmes lehet látni a lefordított kódban, hogy milyen kódot érint (vagy érinthet) az adatbázis növelése.

Az egyetlen igazság

Függetlenül attól, hogy melyik technológiát használja, mindig van egy modell, amely az egyetlen igazságforrás egy bizonyos alrendszer számára - vagy legalábbis erre kell törekednünk, és kerülni kell a vállalati zűrzavart, ahol az "igazság" mindenhol ott van, és egyszerre sehol. Minden sokkal könnyebb lehet. Ha csak XML-fájlokat cserél egy másik rendszerrel, csak használja az XSD-t. Vessen egy pillantást a jOOQ INFORMATION_SCHEMA metamodelljére XML-formátumban:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • Az XSD jól érthető
  • Az XSD nagyon jól megjelöli az XML tartalmat, és minden ügyfélnyelven lehetővé teszi az érvényesítést
  • Az XSD jól verziózott és nagyon visszafelé kompatibilis
  • Az XSD lefordítható Java kódra az XJC használatával

Az utolsó pont fontos. Amikor XML üzenetekkel kommunikálunk egy külső rendszerrel, biztosak akarunk lenni abban, hogy üzeneteink érvényesek. Ez nagyon könnyen elérhető JAXB, XJC és XSD segítségével. Őrültség lenne azt hinni, hogy egy Java-első tervezési megközelítésben, ahol az üzeneteinket Java objektumokként tesszük, valahogyan érthetően renderelhetőek XML-be, és elküldhetők fogyasztásra egy másik rendszerbe. Az így előállított XML nagyon rossz minőségű, dokumentálatlan és nehezen fejleszthető lenne. Ha lenne megegyezés a szolgáltatásminőség szintjéről (SLA) egy ilyen interfészen, azonnal elcsesztenénk.

Hogy őszinte legyek, pontosan ez történik mindig a JSON API-val, de ez egy másik történet, legközelebb vitatkozom...

Adatbázisok: ugyanazok

Ha adatbázisokkal dolgozik, megérti, hogy ezek alapvetően ugyanazok. Az adatbázis birtokolja adatait, és kezelnie kell a sémát. A sémán végrehajtott bármilyen módosítást közvetlenül a DDL-ben kell végrehajtani, hogy az igazság egyetlen forrása frissüljön.

Amikor a forrás frissítése megtörtént, minden ügyfélnek frissítenie kell a modell másolatát is. Egyes kliensek Java nyelven írhatók jOOQ és Hibernate vagy JDBC (vagy mindkettő) használatával. Más kliensek Perlben írhatók (kívánjunk nekik szerencsét), mások C#-ban. Nem számít. A fő modell az adatbázisban található. Az ORM által generált modellek általában rossz minőségűek, rosszul dokumentáltak és nehezen fejleszthetők.

Tehát ne hibázz. Ne kövess el hibákat az elejétől fogva. Munka adatbázisból. Építsen fel egy automatizálható telepítési folyamatot. Engedélyezze a kódgenerátoroknak, hogy kényelmesen másolják az adatbázis-modellt, és kiírják az ügyfelekre. És ne aggódj a kódgenerátorok miatt. Ők jók. Velük produktívabb leszel. Mindössze annyit kell tennie, hogy egy kis időt el kell töltenie a beállításukkal a kezdetektől fogva, és több évnyi jobb teljesítmény áll rendelkezésére projektje történetének felépítéséhez.

Még ne köszönje meg, majd később.

derítés

Az egyértelműség kedvéért: Ez a cikk semmilyen módon nem támogatja azt, hogy a teljes rendszert (azaz a tartományt, az üzleti logikát stb. stb.) rugalmasan kell alakítani, hogy illeszkedjen az adatbázismodellhez. Ebben a cikkben arról beszélek, hogy az adatbázissal kölcsönhatásba lépő ügyfélkódnak az adatbázismodell alapján kell működnie, hogy ne reprodukálja az adatbázismodellt "első osztályú" státuszban. Az ilyen logika általában az ügyfél adatelérési rétegében található.

A helyenként még megőrzött kétszintű architektúrákban ilyen rendszermodell lehet az egyetlen lehetséges. Azonban a legtöbb rendszerben az adatelérési réteg számomra egy "alrendszernek" tűnik, amely magába foglalja az adatbázis-modellt.

kivételek

Minden szabály alól vannak kivételek, és már korábban is mondtam, hogy az adatbázis-első és a forráskód-generálási megközelítés néha nem megfelelő. Íme néhány ilyen kivétel (valószínűleg vannak mások is):

  • Ha a séma ismeretlen, és meg kell nyitni. Például egy olyan eszközt biztosít, amely segít a felhasználóknak bármilyen diagramban navigálni. Fú. Itt nincs kódgenerálás. De mégis - mindenekelőtt az adatbázis.
  • Amikor valamilyen probléma megoldásához menet közben áramkört kell generálni. Ez a példa a minta kissé fodros változatának tűnik entitás attribútum értéke, azaz nem igazán van jól körülhatárolt sémája. Ebben az esetben gyakran egyáltalán nem lehet biztos abban, hogy egy RDBMS megfelel-e Önnek.

A kivételek természetüknél fogva kivételesek. Az RDBMS használatával járó esetek többségében a séma előre ismert, az RDBMS-en belül van, és az egyetlen "igazságforrás", és minden kliensnek be kell szereznie a belőle származó másolatokat. Ideális esetben ennek tartalmaznia kell egy kódgenerátort.

Forrás: will.com

Hozzászólás