Vero unue, aŭ kial sistemo devas esti desegnita surbaze de la datumbaza dezajno

Hej Habr!

Ni daŭre esploras la temon java и printempa, inkluzive ĉe la datumbaza nivelo. Hodiaŭ ni invitas vin legi pri kial, dum desegnado de grandaj aplikoj, estas la datumbaza strukturo, kaj ne la Java-kodo, kiu devas esti decida graveco, kiel tio estas farita, kaj kiaj esceptoj estas al ĉi tiu regulo.

En ĉi tiu sufiĉe malfrua artikolo, mi klarigos kial mi kredas ke en preskaŭ ĉiuj kazoj, la datummodelo en aplikaĵo devus esti desegnita "el la datumbazo" prefere ol "el la kapabloj de Java" (aŭ kia ajn klientlingvo vi estas. laborante kun). Prenante la duan aliron, vi preparas vin por longa vojo de doloro kaj sufero post kiam via projekto komencas kreski.

La artikolo estis verkita surbaze de unu demando, donita sur Stack Overflow.

Interesaj diskutoj pri reddit en sekcioj /r/java и /r/programado.

Kodgenerado

Kiel mi surprizis, ke ekzistas tiom malgranda segmento de uzantoj, kiuj, konatiĝinte kun jOOQ, indignas pro tio, ke jOOQ serioze dependas de fontkoda generacio por funkcii. Neniu malhelpas vin uzi jOOQ kiel vi konvenas, aŭ devigas vin uzi kodgeneradon. Sed la defaŭlta (kiel priskribita en la manlibro) maniero labori kun jOOQ estas ke vi komencas kun (heredaĵo) datumbaza skemo, inversigi ĝin uzante la jOOQ-kodgeneratoron por tiel akiri aron da klasoj reprezentantaj viajn tabelojn, kaj poste skribu tipon. -sekuraj demandoj al ĉi tiuj tabeloj:

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

La kodo estas generita aŭ mane ekstere de la kunigo, aŭ mane ĉe ĉiu asembleo. Ekzemple, tia regenerado povas sekvi tuj poste Flugvoja datumbaza migrado, kiu ankaŭ povas esti farita permane aŭ aŭtomate.

Fontkoda generacio

Estas diversaj filozofioj, avantaĝoj kaj malavantaĝoj asociitaj kun ĉi tiuj aliroj al kodgenerado - manlibro kaj aŭtomata - kiujn mi ne diskutos detale en ĉi tiu artikolo. Sed, ĝenerale, la tuta punkto de la generita kodo estas, ke ĝi permesas al ni reprodukti en Java tiun "veron", kiun ni prenas por koncedita, ĉu ene de nia sistemo, ĉu ekster ĝi. Iasence, tion faras kompililoj kiam ili generas bajtkodon, maŝinkodon aŭ iun alian formon de fontkodo - ni ricevas reprezenton de nia "vero" en alia lingvo, sendepende de la specifaj kialoj.

Estas multaj tiaj kodgeneratoroj. Ekzemple, XJC povas generi Java-kodon bazitan sur XSD aŭ WSDL-dosieroj. La principo estas ĉiam la sama:

  • Estas iu vero (interna aŭ ekstera) - ekzemple specifo, datummodelo ktp.
  • Ni bezonas lokan reprezenton de ĉi tiu vero en nia programlingvo.

Krome, estas preskaŭ ĉiam konsilinde generi tian reprezenton por eviti redundon.

Tipo-Provizantoj kaj Annotation Processing

Notu: alia, pli moderna kaj specifa aliro al generado de kodo por jOOQ uzas tipprovizantoj, ĉar ili estas efektivigitaj en F#. En ĉi tiu kazo, la kodo estas generita de la kompililo, fakte en la kompila stadio. Principe tia kodo ne ekzistas en fontoformo. Java havas similajn, kvankam ne tiel elegantajn, ilojn - komentadajn procesorojn, ekzemple, Lombok.

Iasence okazas ĉi tie la samaj aferoj kiel en la unua kazo, escepte de:

  • Vi ne vidas la generitan kodon (eble ĉi tiu situacio ŝajnas malpli forpuŝa al iu?)
  • Vi devas certigi ke tipoj povas esti provizitaj, tio estas, "vera" devas ĉiam esti havebla. Ĉi tio estas facila en la kazo de Lombok, kiu notas "veron". Ĝi estas iom pli komplika kun datumbazaj modeloj, kiuj dependas de konstante disponebla viva konekto.

Kio estas la problemo kun kodgenerado?

Krom la malfacila demando pri kiel plej bone ruli kodgeneradon - permane aŭ aŭtomate, ni ankaŭ devas mencii, ke ekzistas homoj, kiuj kredas, ke kodgenerado tute ne necesas. La pravigo de ĉi tiu vidpunkto, kiun mi renkontis plej ofte, estas ke tiam estas malfacile starigi konstruan dukton. Jes, estas vere malfacila. Kromaj infrastrukturkostoj ekestas. Se vi ĵus komencas kun aparta produkto (ĉu ĝi estas jOOQ, aŭ JAXB, aŭ Hibernate, ktp.), agordi produktadmedion bezonas tempon, ke vi preferus elspezi lerni la API mem por ke vi povu ĉerpi valoron el ĝi. .

Se la kostoj asociitaj kun la kompreno de la strukturo de la generatoro estas tro altaj, tiam, efektive, la API faris malbonan laboron pri la uzebleco de la kodgeneratoro (kaj poste rezultas, ke uzanta personigo en ĝi ankaŭ estas malfacila). Uzebleco devus esti la plej alta prioritato por tia API. Sed ĉi tio estas nur unu argumento kontraŭ kodgenerado. Alie, estas absolute tute manlibro skribi lokan reprezenton de interna aŭ ekstera vero.

Multaj diros, ke ili ne havas tempon por fari ĉion ĉi. Ili elĉerpas limdatojn por sia Supera Produkto. Iam ni ordigos la asembleajn transportiloj, ni havos tempon. Mi respondos al ili:

Vero unue, aŭ kial sistemo devas esti desegnita surbaze de la datumbaza dezajno
Originala, Alan O'Rourke, Spektantaro-Stako

Sed en Hibernate/JPA estas tiel facile skribi Java-kodon.

Vere. Por Hibernate kaj ĝiaj uzantoj, ĉi tio estas kaj beno kaj malbeno. En Hibernate vi povas simple skribi kelkajn entojn, kiel ĉi tio:

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

Kaj preskaŭ ĉio estas preta. Nun dependas de Hibernate generi la kompleksajn "detalojn" pri kiel ĝuste ĉi tiu ento estos difinita en la DDL de via SQL "dialekto":

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

... kaj komencu ruli la aplikaĵon. Vere bonega ŝanco komenci rapide kaj provi malsamajn aferojn.

Tamen bonvolu permesi al mi. Mi mensogis.

  • Ĉu Hibernate efektive devigos la difinon de ĉi tiu nomita ĉefa ŝlosilo?
  • Ĉu Hibernate kreos indekson en TITLE? – Mi scias certe, ke ni bezonos ĝin.
  • Ĉu Hibernate precize faros ĉi tiun ŝlosilon identigon en la Identeca Specifo?

Verŝajne ne. Se vi disvolvas vian projekton de nulo, ĉiam estas oportune simple forĵeti la malnovan datumbazon kaj generi novan tuj kiam vi aldonas la necesajn komentariojn. Tiel, la Libro-unuo finfine prenos la formon:

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

Cool. Regeneri. Denove, ĉi-kaze ĝi estos tre facila ĉe la komenco.

Sed vi devos pagi por ĝi poste

Pli aŭ malpli frue vi devos iri en produktadon. Jen kiam ĉi tiu modelo ĉesos funkcii. Ĉar:

En produktado, ne plu eblos, se necese, forĵeti la malnovan datumbazon kaj komenci de nulo. Via datumbazo fariĝos heredaĵo.

De nun kaj eterne vi devos skribi DDL-migradskriptoj, ekzemple, uzante Flyway. Kio okazos al viaj entoj en ĉi tiu kazo? Vi povas aŭ adapti ilin permane (kaj tiel duobligi vian laborŝarĝon), aŭ vi povas diri al Hibernate regeneri ilin por vi (kiom verŝajne tiuj generitaj tiel kontentigu viajn atendojn?) Ĉiuokaze, vi perdas.

Do post kiam vi eniros produktadon, vi bezonos varmajn diakilojn. Kaj ili devas esti produktataj tre rapide. Ĉar vi ne preparis kaj ne organizis glatan dukton de viaj migradoj por produktado, vi sovaĝe flikas ĉion. Kaj tiam vi ne plu havas tempon por fari ĉion ĝuste. Kaj vi kritikas Hibernate, ĉar ĉiam estas kulpo de iu alia, nur ne vi...

Anstataŭe, aferoj povus esti faritaj tute alimaniere ekde la komenco mem. Ekzemple, metu rondajn radojn sur biciklon.

Unue datumbazo

La vera "vero" en via datumbaza skemo kaj la "suvereneco" super ĝi kuŝas en la datumbazo. La skemo estas difinita nur en la datumbazo mem kaj nenie aliloke, kaj ĉiu kliento havas kopion de ĉi tiu skemo, do tute sencas devigi la observon kun la skemo kaj ĝia integreco, por fari ĝin ĝuste en la datumbazo - kie la informo estas. stokita.
Ĉi tio estas malnova, eĉ trudita saĝo. Primaraj kaj unikaj ŝlosiloj estas bonaj. Fremdaj ŝlosiloj estas bonaj. Kontroli limigojn estas bona. Deklaroj - Bone.

Cetere, tio ne estas ĉio. Ekzemple, uzante Oracle, vi verŝajne volus specifi:

  • En kiu tablospaco estas via tablo?
  • Kio estas ĝia PCTFREE-valoro?
  • Kio estas la kaŝmemoro en via sinsekvo (malantaŭ la id)

Ĉi tio eble ne gravas en malgrandaj sistemoj, sed vi ne devas atendi ĝis vi translokiĝos al la sfero de grandaj datumoj—vi povas multe pli frue profiti de stokadoptimumoj provizitaj de vendisto kiel tiuj supre menciitaj. Neniu el la ORM-oj kiujn mi vidis (inkluzive de jOOQ) disponigas aliron al la plena aro de DDL-opcioj, kiujn vi eble volas uzi en via datumbazo. ORMoj ofertas iujn ilojn, kiuj helpas vin skribi DDL.

Sed fine de la tago, bone desegnita cirkvito estas mane skribita en DDL. Ajna generita DDL estas nur aproksimado de ĝi.

Kio pri la klienta modelo?

Kiel menciite supre, ĉe la kliento vi bezonos kopion de via datumbaza skemo, la klienta vido. Ne necesas mencii, ke ĉi tiu klienta vido devas esti sinkronigita kun la reala modelo. Kio estas la plej bona maniero atingi ĉi tion? Uzante kodgeneratoron.

Ĉiuj datumbazoj disponigas siajn metainformojn per SQL. Jen kiel akiri ĉiujn tabelojn de via datumbazo en malsamaj SQL-dialektoj:

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

Ĉi tiuj demandoj (aŭ similaj, laŭ ĉu vi ankaŭ devas konsideri vidojn, materiigitajn vidojn, tabel-valorajn funkciojn) ankaŭ estas ekzekutitaj per vokado. DatabaseMetaData.getTables() de JDBC, aŭ uzante la meta-modulon jOOQ.

El la rezultoj de tiaj demandoj, estas relative facile generi ajnan klientflankan reprezentadon de via datumbaza modelo, sendepende de kia teknologio vi uzas sur la kliento.

  • Se vi uzas JDBC aŭ Spring, vi povas krei aron de kordaj konstantoj
  • Se vi uzas JPA, vi povas generi la entojn mem
  • Se vi uzas jOOQ, vi povas generi la metamodelon jOOQ

Depende de kiom da funkcieco estas ofertita de via klienta API (ekz. jOOQ aŭ JPA), la generita meta-modelo povas esti vere riĉa kaj kompleta. Prenu, ekzemple, la eblecon de implicaj kuniĝoj, enkondukite en jOOQ 3.11, kiu dependas de generitaj metainformoj pri la fremdaj ŝlosilaj rilatoj kiuj ekzistas inter viaj tabeloj.

Nun ajna datumbaza pliigo aŭtomate ĝisdatigos la klientkodon. Imagu ekzemple:

ALTER TABLE book RENAME COLUMN title TO book_title;

Ĉu vi vere volus fari ĉi tiun laboron dufoje? En neniu kazo. Simple transdonu la DDL, rulu ĝin tra via konstrua dukto, kaj ricevu la ĝisdatigitan enton:

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

Aŭ la ĝisdatigita jOOQ-klaso. Plej multaj DDL-ŝanĝoj ankaŭ influas semantikon, ne nur sintakson. Sekve, povas esti utile rigardi en la kompilita kodo por vidi kian kodon estos (aŭ povus) esti influita de via datumbaza pliigo.

La sola vero

Sendepende de kia teknologio vi uzas, ĉiam ekzistas unu modelo, kiu estas la sola fonto de vero por iu subsistemo - aŭ, minimume, ni devus strebi por tio kaj eviti tian entreprenan konfuzon, kie "vero" estas ĉie kaj nenie samtempe. . Ĉio povus esti multe pli simpla. Se vi nur interŝanĝas XML-dosierojn kun iu alia sistemo, simple uzu XSD. Rigardu la metamodelon INFORMATION_SCHEMA de jOOQ en XML-formo:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • XSD estas bone komprenita
  • XSD-tokenigas XML-enhavon tre bone kaj permesas validigon en ĉiuj klientlingvoj
  • XSD estas bone versionita kaj havas altnivelan kongruecon
  • XSD povas esti tradukita en Java-kodon per XJC

La lasta punkto estas grava. Kiam ni komunikas kun ekstera sistemo uzante XML-mesaĝojn, ni volas certigi, ke niaj mesaĝoj validas. Ĉi tio estas tre facile atingi uzante JAXB, XJC kaj XSD. Estus pura frenezo pensi ke, kun "Java unue" desegna aliro kie ni faras niajn mesaĝojn kiel Java objektoj, ke ili povus esti iel kohere mapitaj al XML kaj senditaj al alia sistemo por konsumo. XML generita tiel estus de tre malbona kvalito, nedokumentita, kaj malfacile disvolvebla. Se ekzistus interkonsento pri serva nivelo (SLA) por tia interfaco, ni tuj fuŝus ĝin.

Sincere, jen kio okazas la tutan tempon kun JSON-API-oj, sed tio estas alia rakonto, mi kverelos venontfoje...

Datumbazoj: ili estas la sama afero

Laborante kun datumbazoj, vi rimarkas, ke ili ĉiuj esence similas. La bazo posedas siajn datumojn kaj devas administri la skemon. Ĉiuj modifoj faritaj al la skemo devas esti efektivigitaj rekte en la DDL tiel ke la ununura fonto de vero estas ĝisdatigita.

Kiam fonta ĝisdatigo okazis, ĉiuj klientoj ankaŭ devas ĝisdatigi siajn kopiojn de la modelo. Iuj klientoj povas esti skribitaj en Java uzante jOOQ kaj Hibernate aŭ JDBC (aŭ ambaŭ). Aliaj klientoj povas esti skribitaj en Perl (ni nur deziras al ili bonan sorton), dum aliaj povas esti skribitaj en C#. Ne gravas. La ĉefa modelo estas en la datumbazo. Modeloj generitaj uzante ORMojn estas kutime de malbona kvalito, nebone dokumentitaj, kaj malfacile disvolveblaj.

Do ne faru erarojn. Ne faru erarojn de la komenco mem. Laboru el la datumbazo. Konstruu disfaldan dukton kiu povas esti aŭtomatigita. Ebligu kodgenerantojn por faciligi kopii vian datumbazan modelon kaj forĵeti ĝin sur klientojn. Kaj ĉesu zorgi pri kodgeneratoroj. Ili estas bona. Kun ili vi fariĝos pli produktiva. Vi nur bezonas pasigi iom da tempo agordi ilin de la komenco - kaj tiam atendas vin jaroj da pliigita produktiveco, kiuj konsistigos la historion de via projekto.

Ne danku min ankoraŭ, poste.

Klarigo

Por esti klara: Ĉi tiu artikolo neniel rekomendas, ke vi devas fleksi la tutan sistemon (t.e. domajno, komerca logiko, ktp., ktp.) por konveni vian datumbazan modelon. Kion mi diras en ĉi tiu artikolo, estas, ke la klientokodo, kiu interagas kun la datumbazo, agu surbaze de la datumbaza modelo, por ke ĝi mem ne reproduktu la datumbazan modelon en "unua klaso" statuso. Ĉi tiu logiko kutime troviĝas ĉe la datumalira tavolo de via kliento.

En dunivelaj arkitekturoj, kiuj ankoraŭ kelkloke konserviĝas, tia sistemmodelo povas esti la nura ebla. Tamen, en la plej multaj sistemoj la datumalira tavolo ŝajnas al mi esti "subsistemo" kiu enkapsuligas la datumbazan modelon.

Esceptoj

Estas esceptoj al ĉiu regulo, kaj mi jam diris ke la datumbaza unua kaj fontkoda aliro al generado povas foje esti malkonvena. Jen kelkaj tiaj esceptoj (verŝajne estas aliaj):

  • Kiam la skemo estas nekonata kaj devas esti malkovrita. Ekzemple, vi estas provizanto de ilo, kiu helpas uzantojn navigi ajnan diagramon. Uf. Ĉi tie ne ekzistas koda generacio. Sed tamen, la datumbazo venas unue.
  • Kiam cirkvito devas esti generita sur la flugo por solvi iun problemon. Ĉi tiu ekzemplo ŝajnas iomete fantazia versio de la ŝablono valoro de atributo de ento, t.e., vi ja ne havas klare difinitan skemon. En ĉi tiu kazo, vi ofte eĉ ne povas esti certa, ke RDBMS konvenos al vi.

Esceptoj estas nature esceptaj. Plejofte engaĝante la uzon de RDBMS, la skemo estas konata anticipe, ĝi loĝas ene de la RDBMS kaj estas la nura fonto de "vero", kaj ĉiuj klientoj devas akiri kopiojn derivitajn de ĝi. Ideale, vi devas uzi kodgeneratoron.

fonto: www.habr.com

Aldoni komenton