Hej, Habr!
Vi fortsÀtter att utforska Àmnet och , inklusive pÄ databasenivÄ. Idag erbjuder vi att lÀsa om varför databassstruktur, snarare Àn Java-kod, bör ha avgörande betydelse nÀr man designar stora applikationer, hur detta görs och vilka undantag som finns frÄn denna regel.
I den hÀr ganska sena artikeln kommer jag att förklara varför jag anser att data-modellen i en applikation i praktiskt taget alla fall bör designas "utifrÄn databasen", snarare Àn "utifrÄn Java-möjligheterna" (eller nÄgot annat klient-sprÄk som du arbetar med). VÀljer man den andra metoden, dÄ ger man sig in pÄ en lÄng vÀg av smÀrta och lidande, sÄ snart ditt projekt börjar vÀxa.
Artikeln Àr skriven med inspiration frÄn , stÀlld pÄ Stack Overflow.
Intressanta diskussioner pÄ reddit i sektionerna och .
Kodgenerering
Jag blev verkligen förvÄnad över att det finns en sÄ liten skara anvÀndare som, efter att ha bekantat sig med jOOQ, blir upprörda över det faktum att jOOQ allvarligt baseras pÄ kodgenerering. Ingen hindrar dig frÄn att anvÀnda jOOQ som du tycker Àr nödvÀndigt, och ingen tvingar dig att anvÀnda kodgenerering. Men som standard (som beskrivs i manualen) fungerar jOOQ sÄ hÀr: du börjar med (Àrvda) databasscheman, utför omvÀnd ingenjörskonst med hjÀlp av jOOQs kodgenerator för att pÄ sÄ sÀtt fÄ en uppsÀttning klasser som representerar dina tabeller, och sedan skriver du typ-sÀkra frÄgor till dessa tabeller:
for (Record2 record : DSL.using(configuration)
// ^^^^^^^^^^^^^^^^^^^^^^^ Typinformation har hÀrletts frÄn
// den genererade koden vilket refereras av det
// följande SELECT-villkoret
.select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
// vvvvv ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ genererade namn
.from(ACTOR)
.orderBy(1, 2)) {
// ...
}Koden genereras antingen manuellt utanför sammansÀttningen, eller manuellt vid varje sammansÀttning. Till exempel kan sÄdan regenerering följa direkt efter .
Kodgenerering
Med sĂ„dana metoder för kodgenerering â manuella och automatiska â Ă€r knutna olika filosofier, fördelar och nackdelar som jag inte avser att diskutera i detalj i denna artikel. Men i allmĂ€nhet handlar hela essensen av genererad kod om att Ă„terge den 'sanning' vi accepterar som en given, antingen inom vĂ„rt system eller utanför det. PĂ„ ett sĂ€tt gör kompilatorer samma sak, som genererar bytekod, maskinkod eller nĂ„gon annan typ av kod baserat pĂ„ kĂ€llkod â vi fĂ„r en representation av vĂ„r 'sanning' pĂ„ ett annat sprĂ„k, oavsett specifika orsaker.
Det finns mÄnga sÄdana kodgeneratorer. Till exempel, Principen Àr alltid densamma:
- Det finns en viss sanning (intern eller extern) â till exempel specifikation, datamodell, osv.
- Vi behöver en lokal representation av denna sanning pÄ vÄrt programmeringssprÄk.
Och det Ă€r nĂ€stan alltid förnuftigt att generera en sĂ„dan representation â för att undvika överflödighet.
Typleverantörer och bearbetning av annoteringar
Observera: en annan, modernare och mer specifik metod för kodgenerering för jOOQ Ă€r kopplad till anvĂ€ndningen av typleverantörer, I sĂ„dana fall genereras koden av kompilatorn, faktiskt vid kompileringstillfĂ€llet. I form av kĂ€llkod existerar sĂ„dan kod inte egentligen. I Java finns liknande, om Ă€n inte sĂ„dĂ€r eleganta verktyg â det Ă€r annoteringsprocessorer, till exempel, .
PÄ sÀtt och vis hÀnder samma saker hÀr som i det första fallet, med undantag för att:
- Du ser inte den genererade koden (möjligen verkar denna situation inte sÄ avskrÀckande för nÄgon?)
- Du mÄste garantera att typer kan erbjudas, vilket betyder att 'sanningen' alltid mÄste vara tillgÀnglig. Detta Àr lÀtt i fallet med Lombok, som annoterar "sanningen". Lite mer komplicerat Àr det med databasmodeller, vars funktioner beror pÄ att en stÀndig aktiv anslutning finns tillgÀnglig.
Vad Àr problemet med kodgenerering?
Förutom den knepiga frĂ„gan om hur man bĂ€st ska starta kodgenereringen â manuellt eller automatiskt â mĂ„ste man ocksĂ„ nĂ€mna att det finns personer som anser att kodgenerering överhuvudtaget inte behövs. Den mest frekventa motiveringen för denna synpunkt som jag har stött pĂ„ Ă€r att det dĂ„ Ă€r svĂ„rt att konfigurera byggpipeline. Ja, det Ă€r verkligen svĂ„rt. Det uppstĂ„r ytterligare infrastrukturkostnader. Om du just har börjat arbeta med en viss produkt (oavsett om det Ă€r jOOQ, JAXB, Hibernate, etc.), tar det tid att stĂ€lla in arbetsmiljön, en tid som du hellre skulle lĂ€gga pĂ„ att studera API:et och sĂ„ smĂ„ningom dra nytta av det.
Om kostnaderna för att förstĂ„ hur generatorn fungerar Ă€r för höga â dĂ„ har API:et verkligen inte arbetat tillrĂ€ckligt med anvĂ€ndarvĂ€nligheten för kodgeneratorn (och det visar sig senare att Ă€ven anvĂ€ndarinstĂ€llningar Ă€r svĂ„ra). AnvĂ€ndarvĂ€nlighet bör alltid vara högsta prioritet för ett sĂ„dant API. Men det hĂ€r Ă€r bara ett argument mot kodgenerering. I övrigt Ă€r det helt och hĂ„llet nödvĂ€ndigt att skriva en lokal representation av interna eller externa sanningar för hand.
MÄnga kommer att sÀga att de inte har tid för detta. De har strikta deadlines för sin Super-Produkt. Senare fÄr vi uppdatera byggpipelines, det finns tid. Jag responderar:

,
Men i Hibernate / JPA Àr det sÄ enkelt att skriva kod "för Java".
Verkligen. För Hibernate och dess anvÀndare Àr detta bÄde en vÀlsignelse och en förbannelse. I Hibernate kan man enkelt skriva ett par entiteter, sÄ hÀr:
@Entity
class Book {
@Id
int id;
String title;
}Och nĂ€stan allt Ă€r klart. Nu Ă€r det Hibernates uppgift â att generera komplexa "detaljer" om hur just denna entitet kommer att definieras i DDL för din "dialekt" 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);⊠och vi börjar köra applikationen. Verkligen fantastisk möjlighet att snabbt komma igÄng och testa olika saker.
Men vÀnta. Jag har överdrivit.
- Kommer Hibernate verkligen att anvÀnda definitionen av denna namngivna primÀra nyckel?
- Kommer Hibernate att skapa ett index pĂ„ TITLE? â jag vet att vi kommer att behöva det.
- Kommer Hibernate verkligen att göra denna nyckel identifierande i Identity Specification?
Troligen inte. Om du utvecklar ditt projekt frÄn grunden, Àr det alltid praktiskt att bara slÀppa den gamla databasen och generera en ny sÄ snart du har lagt till nödvÀndiga annoteringar. SÄ, entiteten Book kommer sÄ smÄningom att se ut sÄ hÀr:
@Entity
@Table(name = "book", indexes = {
@Index(name = "i_book_title", columnList = "title")
})
class Book {
@Id
@GeneratedValue(strategy = IDENTITY)
int id;
String title;
}
Coolt. Generera om. à terigen, i sÄ fall kommer det att bli mycket enkelt i början.
Men senare kommer du att fÄ betala för det.
Förr eller senare mÄste du gÄ i produktion. Just dÄ kommer denna modell att sluta fungera. För att:
I produktion kan du inte lÀngre kasta bort den gamla databasen och börja om frÄn början. Din databas kommer att bli en arvtagare.
FrÄn och med nu och för alltid mÄste du skriva . Och vad hÀnder i sÄ fall med dina entiteter? Du kan antingen anpassa dem manuellt (och pÄ sÄ sÀtt dubbla din arbetsbelastning), eller be Hibernate generera dem igen Ät dig (hur stora Àr chanserna att det som genereras pÄ detta sÀtt motsvarar dina förvÀntningar?) Du förlorar oavsett.
DĂ€rför, sĂ„ snart du gĂ„r i produktion, behöver du snabba patchar. Och de mĂ„ste slĂ€ppas i produktion mycket snabbt. Eftersom du inte var förberedd och inte organiserade en smidig process för dina migrationer till produktion, kommer du att patcha överallt. Och sedan hinner du inte göra allt korrekt. Och skyller pĂ„ Hibernate, eftersom alltid nĂ„gon annan Ă€r skyldig, utom duâŠ
IstÀllet kunde allt ha gjorts helt annorlunda frÄn början. Till exempel, sÀtta pÄ cykeln runda hjul.
Först databasen.
Den verkliga "sanningen" i strukturen för din databas och "suverĂ€niteten" över den finns inne i databasen. Strukturen definieras endast i sjĂ€lva databasen och ingen annanstans, och varje kund har en kopia av denna struktur, sĂ„ det Ă€r helt rimligt att föreskriva överensstĂ€mmelse med strukturen och dess integritet, göra det direkt i databasen â dĂ€r informationen lagras.
Detta Ă€r en gammal och till och med slitstark visdom. PrimĂ€ra och unika nycklar Ă€r bra. Utlandsnycklar â bra. Kontroller av begrĂ€nsningar â bra. â bra.
Dessutom Àr detta inte allt. Om du anvÀnder Oracle, vill du troligen specificera:
- I vilket tablespace din tabell finns
- Vad dess PCTFREE-vÀrde Àr
- Vad storleken pÄ cachen i din sekvens (efter identifieraren) Àr
Detta kanske inte betyder sĂ„ mycket i mindre system, men du behöver inte vĂ€nta med att gĂ„ över till "big data"-omrĂ„det â du kan börja dra nytta av de lagringsoptimeringar som leverantören erbjuder mycket tidigare, som de ovan nĂ€mnda. Ingen av de ORM:er jag har sett (inklusive jOOQ) ger Ă„tkomst till hela uppsĂ€ttningen av DDL-alternativ som du kanske vill anvĂ€nda i din databas. ORM:er erbjuder vissa verktyg som hjĂ€lper till att skriva DDL.
Men slutligen Àr en vÀl utformad struktur skriven för hand i DDL. All genererad DDL Àr bara en approximation av den.
Vad hÀnder med klientmodellen?
Som tidigare nÀmnts kommer du att behöva en kopia av din databasstruktur pÄ klienten, ett klientvisning. Det Àr överflödigt att nÀmna att detta klientvisning mÄste synkroniseras med den faktiska modellen. Hur gör man detta pÄ bÀsta sÀtt? Genom en kodgenerator.
Alla databaser tillhandahÄller sin metadata genom SQL. HÀr Àr hur du fÄr alla tabeller frÄn din databas pÄ olika SQL-dialekter:
-- 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
Dessa frÄgor (eller liknande, beroende pÄ om vyer, materialiserade vyer och fuktiga funktioner ocksÄ beaktas) genomförs ocksÄ med ett anrop frÄn JDBC, eller med hjÀlp av jOOQ:s meta-modul.
FrÄn resultaten av sÄdana frÄgor Àr det relativt enkelt att generera vilket klientvisning av din databasmodell som helst, oavsett vilken teknik du anvÀnder pÄ klienten.
- Om du anvÀnder JDBC eller Spring, kan du skapa en uppsÀttning strÀngkonstanter
- Om du anvÀnder JPA, kan du generera sjÀlva entiteterna
- Om du anvÀnder jOOQ, kan du generera jOOQ:s meta-modell
Beroende pÄ vilken mÀngd funktioner som erbjuds av ditt klient-API (t.ex. jOOQ eller JPA), kan den genererade meta-modellen vara verkligt rik och komplett. LÄt oss ta en titt pÄ möjligheten för implicita sammanfogningar, , vilken bygger pÄ den genererade metainformationen om relationerna mellan dina tabeller.
Nu kommer varje Àndring av databasen automatiskt att leda till en uppdatering av klientkoden. FörestÀll dig till exempel:
ALTER TABLE book RENAME COLUMN title TO book_title;Vill du verkligen göra det hÀr arbetet tvÄ gÄnger? Absolut inte. Vi registrerar bara DDL, kör det genom din byggpipeline och fÄr den uppdaterade entiteten:
@Entity
@Table(name = "book", indexes = {
// Har du tÀnkt pÄ detta?
@Index(name = "i_book_title", columnList = "book_title")
})
class Book {
@Id
@GeneratedValue(strategy = IDENTITY)
int id;
@Column("book_title")
String bookTitle;
}Eller den uppdaterade jOOQ-klassen. De flesta DDL-förÀndringar Äterspeglas ocksÄ i semantiken snarare Àn bara i syntaxen. DÀrför kan det vara praktiskt att titta i den kompilerade koden för att se vilken kod som kommer (eller kan) beröras av Àndringen av din databas.
Den enda sanningen
Oavsett vilken teknologi du anvĂ€nder, finns det alltid en modell som Ă€r den enda sanningskĂ€llan för en viss subsystem â eller Ă„tminstone bör vi strĂ€va efter detta och undvika den dĂ€r företagsförvirringen, dĂ€r "sanningen" Ă€r bĂ„de överallt och ingenstans. Allt kan vara mycket enklare. Om du bara utbyter XML-filer med nĂ„got annat system, anvĂ€nd dĂ„ bara XSD. Titta pĂ„ meta-modellen INFORMATION_SCHEMA frĂ„n jOOQ i XML-form:
- XSD Àr lÀtt att förstÄ
- XSD strukturerar XML-innehÄllet mycket vÀl och möjliggör validering pÄ alla klientsprÄk
- XSD versioneras vÀl och har bra bakÄtkompatibilitet
- XSD kan översÀttas till Java-kod med hjÀlp av XJC
Den sista punkten Àr viktig. NÀr vi kommunicerar med ett externt system med hjÀlp av XML-meddelanden vill vi vara sÀkra pÄ att vÄra meddelanden Àr giltiga. Detta kan lÀtt uppnÄs med JAXB, XJC och XSD. Det skulle vara rent galet att tro att, med en "Java-först"-design dÀr vi skapar vÄra meddelanden som Java-objekt, kan dessa pÄ nÄgot sÀtt tydligt avbildas pÄ XML och skickas för konsumtion i ett annat system. XML som genereras pÄ detta sÀtt skulle vara av mycket dÄlig kvalitet, icke-dokumenterat och svÄrt att underhÄlla. Om det fanns ett avtal om servicenivÄer (SLA) för ett sÄdant grÀnssnitt skulle vi omedelbart ha krossat det.
Ărligt talat, det hĂ€r hĂ€nder stĂ€ndigt med JSON-API:er, men det Ă€r en annan historia, nĂ€sta gĂ„ng ska jag klagaâŠ
Databaser: det Àr samma sak
NÀr du arbetar med databaser förstÄr du att de alla, i princip, liknar varandra. En databas Àger sina egna data och ska styra schemat. Eventuella Àndringar som görs i schemat mÄste genomföras direkt pÄ DDL för att uppdatera den enda sanningskÀllan.
NÀr uppdateringen av kÀllan har skett mÄste alla klienter ocksÄ uppdatera sina kopior av modellen. Vissa klienter kan vara skrivna i Java med jOOQ och Hibernate eller JDBC (eller alla pÄ en gÄng). Andra klienter kan vara skrivna i Perl (jag önskar dem lycka till), tredje pÄ C#. Det spelar ingen roll. Huvudmodellen finns i databasen. Modeller som genereras med hjÀlp av ORM Àr vanligtvis av dÄlig kvalitet, dÄligt dokumenterade och svÄra att utveckla.
SĂ„ gör inga misstag. Gör inga misstag frĂ„n början. Arbeta utifrĂ„n databasen. Bygg en distributionspipeline som kan automatiseras. Inkludera kodgeneratorer för att enkelt kopiera modellen av din databas och överföra den till klienterna. Sluta oroa dig för kodgeneratorer. De Ă€r bra. Med dem kommer du att bli mer produktiv. Det krĂ€vs bara lite tid frĂ„n början för att konfigurera dem â och sedan vĂ€ntar Ă„r av ökad produktivitet som kommer att vĂ€va din projekthistoria.
Tacka mig inte Àn, senare.
Förklaring
För tydlighetens skull: Denna artikel propagerar pÄ inget sÀtt för att hela systemet (det vill sÀga ÀmnesomrÄdet, affÀrslogiken, etc.) ska anpassas efter din databasmodell. I denna artikel diskuterar jag hur klientkoden som interagerar med databasen bör fungera utifrÄn databasmodellen, sÄ att sjÀlva klientkoden inte reproducerar databasmodellen som "första klass". Denna logik placeras vanligtvis pÄ datÄtkomstnivÄn hos dig pÄ klienten.
I tvÄnivÄarkitekturer, som fortfarande finns kvar pÄ vissa stÀllen, kan en sÄdan systemmodell vara den enda möjliga. Men i de flesta system verkar datÄtkomstnivÄn för mig som en "undersystem" som inkapslar databasmodellen.
Undantag
Det finns undantag frÄn varje regel, och jag har redan nÀmnt att den primÀra databastillvÀgagÄngssÀttet och koden som genereras ibland kan vara olÀmpliga. HÀr Àr ett par sÄdana undantag (det troligen finns fler):
- NĂ€r schemat Ă€r okĂ€nt och behöver öppnas. Till exempel, om du Ă€r en leverantör av verktyg som hjĂ€lper anvĂ€ndare att navigera i vilket schema som helst. Puh. HĂ€r behövs ingen kodgenerering. Men oavsett â databasen först.
- NÀr schemat mÄste genereras dynamiskt för att lösa ett specifikt problem. Detta exempel verkar vara en nÄgot överdriven version av mönstret , det vill sÀga, du har faktiskt ingen definierad modell. I sÄdana fall kan det ofta vara svÄrt att vara sÀker pÄ att en RDBMS passar dina behov.
Undantag Àr per definition undantagsfall. I de flesta fall som involverar anvÀndningen av RDBMS Àr schemat kÀnt pÄ förhand, det finns inuti RDBMS och Àr den enda kÀllan till "sanning", och alla klienter mÄste ha kopior som Àr derivat av det. Idealiskt bör en kodgenerator anvÀndas.
KĂ€lla: habr.com
