Verità prima, o perchè u sistema deve esse designatu basatu annantu à a struttura di basa di dati

Ehi Habr!

Cuntinuemu a ricerca di u tema Java и primavera, ancu à u livellu di basa di dati. Oghje vi invitamu à leghje perchè, quandu si cuncepisce grandi applicazioni, hè a struttura di a basa di dati, è micca u codice Java, chì deve esse d'impurtanza decisiva, cumu si fa questu, è chì eccezzioni ci sò à sta regula.

In questu articulu piuttostu tardu, spiegheraghju perchè crede chì in quasi tutti i casi, u mudellu di dati in una applicazione deve esse designatu "da a basa di dati" piuttostu cà "da e capacità di Java" (o qualunque lingua di u cliente site). travaglià cù). Pigliendu u sicondu approcciu, vi mette in una longa strada di dulore è soffrenu quandu u vostru prughjettu cumencia à cresce.

L'articulu hè statu scrittu basatu annantu una quistione, datu nantu à Stack Overflow.

Discussioni interessanti nantu à reddit in sezioni /r/java и /r/programmazione.

Generazione di codice

Quantu era sorpresu chì ci hè un segmentu cusì chjucu di l'utilizatori chì, dopu avè familiarizatu cù jOOQ, sò indignati da u fattu chì jOOQ si basa seriamente in a generazione di codice fonte per operare. Nimu ùn vi impedisce di utilizà jOOQ cum'è vo vede bè, o vi furzà à utilizà a generazione di codice. Ma u modu predeterminatu (cum'è discrittu in u manuale) di travaglià cù jOOQ hè chì avete principiatu cù un schema di basa di dati (legatu), ingegneria inversa utilizendu u generatore di codice jOOQ per ottene cusì un inseme di classi chì rapprisentanu e vostre tavule, è dopu scrive u tipu. - dumande sicure à queste tabelle:

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

U codice hè generatu manualmente fora di l'assemblea, o manualmente in ogni assemblea. Per esempiu, una tale regenerazione pò seguità immediatamente dopu Migrazione di basa di dati Flyway, chì pò ancu esse fatta manualmente o automaticamente.

Generazione di codice fonte

Ci sò parechje filusufie, vantaghji è svantaghji assuciati cù questi approcci à a generazione di codice - manuale è automaticu - chì ùn aghju micca discututu in dettagliu in questu articulu. Ma, in generale, u puntu tutale di u codice generatu hè chì ci permette di ripruduce in Java quella "verità" chì pigliamu per scontru, sia in u nostru sistema sia fora di ellu. In un certu sensu, questu hè ciò chì i compilatori facenu quandu generanu bytecode, codice macchina, o qualchì altra forma di codice fonte - avemu una rapprisintazioni di a nostra "verità" in un'altra lingua, indipendentemente da e ragioni specifiche.

Ci sò parechji generatori di codice. Per esempiu, XJC pò generà codice Java basatu nantu à i schedari XSD o WSDL. U principiu hè sempre u listessu:

  • Ci hè una certa verità (interna o esterna) - per esempiu, una specificazione, un mudellu di dati, etc.
  • Avemu bisognu di una rapprisintazioni lucale di sta verità in a nostra lingua di prugrammazione.

Inoltre, hè quasi sempre cunsigliatu di generà una tale rapprisentazione per evità a redundanza.

Furnisseurs di tipu è Trattamentu di annotazione

Nota: un altru approcciu più mudernu è specificu per a generazione di codice per jOOQ hè aduprendu fornitori di tipu, cum'elli sò implementati in F#. In questu casu, u codice hè generatu da u compilatore, in realtà in u stadiu di compilazione. In principiu, tali codice ùn esiste micca in forma di fonte. Java hà arnesi simili, ma micca cusì eleganti - processori di annotazione, per esempiu, Lombok.

In un certu sensu, i stessi cose succede quì cum'è in u primu casu, cù l'eccezzioni di:

  • Ùn vede micca u codice generatu (forse sta situazione pare menu repulsiva à qualchissia?)
  • Avete da assicurà chì i tipi ponu esse furniti, vale à dì, "veru" deve esse sempre dispunibule. Questu hè faciule in u casu di Lombok, chì annotate "verità". Hè un pocu più complicatu cù mudelli di basa di dati chì dependenu di una cunnessione in diretta sempre dispunibule.

Chì ci hè u prublema cù a generazione di codice?

In più di a quistione complicata di cumu megliu per eseguisce a generazione di codice - manualmente o in autumàticu, avemu ancu menzionatu chì ci sò persone chì crede chì a generazione di codice ùn hè micca necessariu. A ghjustificazione di stu puntu di vista, chì aghju scontru più spessu, hè chì hè allora difficiule di stabilisce un pipeline di custruzzione. Iè, hè veramente difficiule. I costi supplementari di l'infrastruttura sò. Sè vo site appena principiatu cù un pruduttu particulari (sia jOOQ, o JAXB, o Hibernate, etc.), a creazione di un ambiente di produzzione richiede u tempu chì preferite spende à amparà l'API stessa per pudè estrae u valore da ellu. .

Sì i costi assuciati à capiscenu a struttura di u generatore sò troppu altu, allora, veramente, l'API hà fattu un mistieru in l'usabilità di u generatore di codice (è più tardi hè chì a persunalizazione di l'utilizatori hè ancu cumplessa). L'usabilità deve esse a più alta priorità per qualsiasi tali API. Ma questu hè solu un argumentu contru a generazione di codice. Altrimenti, hè assolutamente completamente manuale per scrive una rapprisintazioni lucale di a verità interna o esterna.

Parechje diceranu chì ùn anu micca tempu per fà tuttu questu. Anu esce da i termini per u so Super Product. Qualchì ghjornu mettimu in ordine i trasportatori di assemblea, averemu u tempu. li risponderaghju :

Verità prima, o perchè u sistema deve esse designatu basatu annantu à a struttura di basa di dati
Originale, Alan O'Rourke, Audience Stack

Ma in Hibernate / JPA hè cusì faciule di scrive codice Java.

Veramente. Per Hibernate è i so utilizatori, questu hè à tempu una benedizzione è una maledizione. In Hibernate pudete simpricimenti scrive un paru di entità, cum'è questu:

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

È quasi tuttu hè prontu. Avà tocca à Hibernate generà i "dettagli" cumplessi di cumu esattamente sta entità serà definita in u DDL di u vostru "dialettu" 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);

... è cuminciate à eseguisce l'applicazione. Una opportunità veramente bella per inizià rapidamente è pruvà cose diverse.

Tuttavia, per piacè permettemi. Eru mentitu.

  • Hibernate hà da realizà a definizione di sta chjave primaria chjamata?
  • Hibernate creà un indice in TITLE ? – Sò sicuru ch’ellu ci vulerà.
  • Hibernate farà esattamente sta chjave identificativa in a Specifica d'identità?

Probabilmente micca. Sè vo sviluppate u vostru prughjettu da zero, hè sempre cunvenutu di scartà a basa di dati antica è generà una nova appena aghjunghje l'annotazioni necessarie. Cusì, l'entità di u Libru in fine piglià a forma:

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

Cool. Rigenerate. In novu, in questu casu, serà assai faciule à u principiu.

Ma duverete pagà per questu dopu

Prima o dopu vi tuccherà à andà in pruduzzione. Hè quandu stu mudellu smette di travaglià. Perchè:

In a pruduzzione, ùn serà più pussibule, se ne necessariu, di scaccià a vechja basa di dati è cumincià da zero. A vostra basa di dati diventerà eredità.

Da avà è per sempre vi tuccherà à scrive Scripts di migrazione DDL, per esempiu, utilizendu Flyway. Chì succede à e vostre entità in questu casu? Pudete adattà manualmente (è cusì duppià a vostra carica di travagliu), o pudete dì à Hibernate di rigenerà per voi (quantu sò probabili chì quelli generati in questu modu per risponde à e vostre aspettative?) In ogni modu, perde.

Dunque, una volta entrati in a produzzione, avete bisognu di patch caldi. È anu da esse messu in pruduzzione assai rapidamente. Siccomu ùn avete micca preparatu è ùn avete micca urganizatu una pipeline liscia di e vostre migrazioni per a produzzione, patchedly patch tuttu. È tandu ùn avete più tempu di fà tuttu bè. È critichi Hibernate, perchè hè sempre a culpa di qualcunu altru, solu micca voi...

Invece, e cose puderianu esse fattu di manera completamente diversa da u principiu. Per esempiu, mette roti tondi nantu à una bicicletta.

Database prima

A vera "verità" in u vostru schema di basa di dati è a "sovranità" sopra si trova in a basa di dati. U schema hè definitu solu in a basa di dati stessu è in nudda parte, è ogni cliente hà una copia di stu schema, cusì hè sensu perfettu per rinfurzà u rispettu di u schema è a so integrità, per fà bè in a basa di dati - induve l'infurmazione hè. guardatu.
Questa hè una saviezza vechja, ancu strutta. I chjavi primari è unichi sò boni. I chjavi straneri sò boni. Cuntrollà e restrizioni hè bonu. Dichjarazioni - Va bè.

Inoltre, ùn hè micca tuttu. Per esempiu, usendu Oracle, probabilmente vulete specificà:

  • In quale spaziu di tavola hè a vostra tavola?
  • Chì ghjè u so valore PCTFREE?
  • Chì ghjè a dimensione di cache in a vostra sequenza (darrettu à l'id)

Questu pò esse micca impurtante in i sistemi chjuchi, ma ùn avete micca bisognu di aspittà finu à chì si move in u regnu di big data - pudete cumincià à prufittà di l'ottimisazioni di almacenamiento furnite da u venditore cum'è quelli citati sopra assai prima. Nisunu di l'ORM chì aghju vistu (cumpresu jOOQ) furnisce l'accessu à l'inseme cumpletu di l'opzioni DDL chì pudete vulete usà in a vostra basa di dati. L'ORM offrenu qualchi strumenti chì aiutanu à scrive DDL.

Ma à a fine di u ghjornu, un circuitu ben cuncepitu hè scrittu à manu in DDL. Ogni DDL generatu hè solu una approssimazione di questu.

Chì ci hè u mudellu di u cliente?

Cumu l'esitatu sopra, nantu à u cliente avete bisognu di una copia di u vostru schema di basa di dati, a vista di u cliente. Inutili, sta vista di u cliente deve esse in sincronia cù u mudellu attuale. Chì ghjè u megliu modu per ottene questu? Utilizà un generatore di codice.

Tutte e basa di dati furnisce a so meta infurmazione via SQL. Eccu cumu uttene tutte e tavule da a vostra basa di dati in diversi dialetti 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

Queste dumande (o simili, secondu s'ellu avete ancu cunsiderà viste, viste materializzate, funzioni di valori di tabella) sò ancu eseguite chjamendu. DatabaseMetaData.getTables() da JDBC, o utilizendu u meta-modulu jOOQ.

Da i risultati di tali dumande, hè relativamente faciule di generà qualsiasi rapprisentazione di u cliente di u vostru mudellu di basa di dati, indipendentemente da a tecnulugia chì utilizate nantu à u cliente.

  • Sè vo aduprate JDBC o Spring, pudete creà un settore di custanti di stringa
  • Se utilizate JPA, pudete generà l'entità stessu
  • Se utilizate jOOQ, pudete generà u meta-mudellu jOOQ

Sicondu a quantità di funziunalità hè offerta da u vostru API di u cliente (per esempiu, jOOQ o JPA), u meta modellu generatu pò esse veramente riccu è cumpletu. Pigliate, per esempiu, a pussibilità di unione implicita, introduttu in jOOQ 3.11, chì si basa nantu à meta infurmazione generata nantu à e relazioni chjave straneri chì esistenu trà e vostre tavule.

Avà ogni incrementu di basa di dati aghjurnà automaticamente u codice di u cliente. Imagine per esempiu:

ALTER TABLE book RENAME COLUMN title TO book_title;

Vulete veramente fà stu travagliu duie volte? In nisun casu. Simply commit the DDL, run it through your build pipeline, è uttene l'entità aghjurnata:

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

O a classa jOOQ aghjurnata. A maiò parte di i cambiamenti DDL afectanu ancu a semantica, micca solu a sintassi. Per quessa, pò esse utile à circà in u codice compilatu per vede quale codice serà (o puderia) esse affettatu da l'incrementu di a vostra basa di dati.

A sola verità

Indipendentemente da a tecnulugia chì aduprate, ci hè sempre un mudellu chì hè l'unica fonte di verità per qualchì subsistema - o, à u minimu, duvemu sforzà per questu è evità una tale cunfusione di l'impresa, induve a "verità" hè in ogni locu è in nulla à tempu. . Tuttu puderia esse assai più simplice. Sè vo site solu scambià i schedari XML cù un altru sistema, basta aduprà XSD. Fighjate à u meta-mudellu INFORMATION_SCHEMA da jOOQ in forma XML:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • XSD hè ben capitu
  • XSD tokenizeghja assai bè u cuntenutu XML è permette a validazione in tutte e lingue di u cliente
  • XSD hè ben versionatu è hà una cumpatibilità avanzata in retrocede
  • XSD pò esse traduttu in codice Java cù XJC

L'ultimu puntu hè impurtante. Quandu cumunicà cù un sistema esternu cù missaghji XML, vulemu esse sicuru chì i nostri missaghji sò validi. Questu hè assai faciule da ottene cù JAXB, XJC è XSD. Saria pura pazzia di pensà chì, cù un approcciu di cuncepimentu "Java first" induve facemu i nostri messagi cum'è oggetti Java, ch'elli puderanu in qualchì manera esse mappati in modu coerente à XML è mandati à un altru sistema per u cunsumu. L'XML generatu in questu modu seria di qualità assai povera, micca documentatu è difficiule di sviluppà. S'ellu ci era un accordu di livellu di serviziu (SLA) per una tale interfaccia, l'avemu subitu sguassatu.

Onestamente, questu hè ciò chì succede sempre cù l'API JSON, ma questu hè un'altra storia, litigheraghju a prossima volta...

Databases: sò a stessa cosa

Quandu u travagliu cù basa di dati, capisci chì sò tutti basicamente simili. A basa pussede i so dati è deve gestisce u schema. Ogni mudificazione fatta à u schema deve esse implementata direttamente in u DDL per chì a sola fonte di verità hè aghjurnata.

Quandu una aghjurnazione di fonte hè accaduta, tutti i clienti anu ancu aghjurnà e so copie di u mudellu. Certi clienti ponu esse scritti in Java cù jOOQ è Hibernate o JDBC (o i dui). L'altri clienti ponu esse scritti in Perl (li vulemu solu bona furtuna), mentre chì altri ponu esse scritti in C#. Ùn importa micca. U mudellu principale hè in a basa di dati. I mudelli generati cù l'ORM sò generalmente di mala qualità, pocu documentati è difficili da sviluppà.

Allora ùn fate micca sbagli. Ùn fate micca sbagli da u principiu. U travagliu da a basa di dati. Custruite un pipeline di implementazione chì pò esse automatizatu. Attivate i generatori di codice per fà fà fà più faciule per copià u vostru mudellu di basa di dati è scaricallu nantu à i clienti. È smette di preoccupari di i generatori di codice. Sò boni. Cù elli diventerai più pruduttivu. Basta à passà un pocu di tempu à stallà da u principiu - è dopu anni di produtividade aumentata vi aspettanu, chì custituiscenu a storia di u vostru prughjettu.

Ùn mi ringraziate ancu più tardi.

Spiegazione

Per esse chjaramente: Questu articulu ùn hè micca favurevule chì avete bisognu di piegà tuttu u sistema (vale à dì, duminiu, logica cummerciale, etc., etc.) per adattà à u vostru mudellu di basa di dati. Ciò chì dicu in questu articulu hè chì u codice di u cliente chì interagisce cù a basa di dati deve agisce nantu à a basa di u mudellu di basa di dati, perchè ellu stessu ùn riproduce micca u mudellu di basa di dati in un statutu di "prima classe". Questa logica hè generalmente situata à a capa d'accessu di dati nantu à u vostru cliente.

In l'architettura di dui livelli, chì sò sempre cunservati in certi lochi, un tali mudellu di sistema pò esse l'unicu pussibule. In ogni casu, in a maiò parte di i sistemi, a capa d'accessu à i dati mi pari di esse un "subsistema" chì incapsula u mudellu di basa di dati.

Eccezziune

Ci sò eccezzioni à ogni regula, è aghju digià dettu chì l'approcciu di generazione di basa di dati prima è codice fonte pò esse à volte inappropriatu. Eccu un paru di tali eccezzioni (ci sò probabilmente altri):

  • Quandu u schema hè scunnisciutu è deve esse scupertu. Per esempiu, site un fornitore di un strumentu chì aiuta l'utilizatori à navigà in ogni diagramma. Ugh. Ùn ci hè micca generazione di codice quì. Ma sempre, a basa di dati vene prima.
  • Quandu un circuitu deve esse generatu nantu à a mosca per risolve qualchì prublema. Questu esempiu pare cum'è una versione ligeramente fantastica di u mudellu valore di l'attributu di l'entità, vale à dì, ùn avete micca veramente un schema chjaramente definitu. In questu casu, spessu ùn pò ancu esse sicuru chì un RDBMS vi cunvene.

L'eccezzioni sò per natura eccezziunale. In a maiò parte di i casi chì implicanu l'usu di un RDBMS, u schema hè cunnisciutu in anticipu, reside in u RDBMS è hè l'unica fonte di "verità", è tutti i clienti anu da acquistà copie derivate da questu. Ideale, avete bisognu di utilizà un generatore di codice.

Source: www.habr.com

Add a comment