Katotohanan muna, o kung bakit kailangang idisenyo ang isang sistema batay sa disenyo ng database

Hoy Habr!

Patuloy kaming nagsasaliksik sa paksa Java ΠΈ tagsibol, kabilang sa antas ng database. Ngayon inaanyayahan ka naming basahin ang tungkol sa kung bakit, kapag nagdidisenyo ng malalaking application, ang istraktura ng database, at hindi ang Java code, ang dapat na napakahalaga, kung paano ito ginagawa, at kung anong mga eksepsiyon ang mayroon sa panuntunang ito.

Sa medyo huli na artikulong ito, ipapaliwanag ko kung bakit naniniwala ako na sa halos lahat ng kaso, ang modelo ng data sa isang application ay dapat na idinisenyo "mula sa database" sa halip na "mula sa mga kakayahan ng Java" (o anumang wika ng kliyente na ginagamit mo. nagtatrabaho kasama). Sa pamamagitan ng pagkuha ng pangalawang diskarte, itinatakda mo ang iyong sarili para sa isang mahabang landas ng sakit at pagdurusa kapag nagsimulang lumaki ang iyong proyekto.

Ang artikulo ay isinulat batay sa isang tanong, ibinigay sa Stack Overflow.

Mga kawili-wiling talakayan sa reddit sa mga seksyon /r/java ΠΈ /r/pagprograma.

Pagbuo ng code

Laking gulat ko na may napakaliit na segment ng mga user na, na nakilala ang jOOQ, ay nagagalit sa katotohanan na ang jOOQ ay seryosong umaasa sa pagbuo ng source code upang gumana. Walang pumipigil sa iyo sa paggamit ng jOOQ ayon sa iyong nakikita, o pinipilit kang gumamit ng pagbuo ng code. Ngunit ang default (tulad ng inilarawan sa manual) na paraan ng pagtatrabaho sa jOOQ ay ang magsimula ka sa isang (legacy) database schema, i-reverse engineer ito gamit ang jOOQ code generator upang sa gayon ay makakuha ng isang hanay ng mga klase na kumakatawan sa iyong mga talahanayan, at pagkatapos ay isulat ang uri -safe na mga query sa mga talahanayang ito:

	for (Record2<String, String> record : DSL.using(configuration)
//   ^^^^^^^^^^^^^^^^^^^^^^^ Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ Ρ‚ΠΈΠΏΠ°Ρ… Π²Ρ‹Π²Π΅Π΄Π΅Π½Π° Π½Π° 
//   основании сгСнСрированного ΠΊΠΎΠ΄Π°, Π½Π° ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ссылаСтся ΠΏΡ€ΠΈΠ²Π΅Π΄Π΅Π½Π½ΠΎΠ΅
// Π½ΠΈΠΆΠ΅ условиС SELECT 
 
       .select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
//           vvvvv ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^ сгСнСрированныС ΠΈΠΌΠ΅Π½Π°
       .from(ACTOR)
       .orderBy(1, 2)) {
    // ...
}

Ang code ay nabuo nang manu-mano sa labas ng pagpupulong, o manu-mano sa bawat pagpupulong. Halimbawa, ang naturang pagbabagong-buhay ay maaaring sumunod kaagad pagkatapos Flyway database migration, na maaari ding gawin nang manu-mano o awtomatiko.

Pagbuo ng source code

Mayroong iba't ibang mga pilosopiya, pakinabang at kawalan na nauugnay sa mga pamamaraang ito sa pagbuo ng code - manu-mano at awtomatiko - na hindi ko tatalakayin nang detalyado sa artikulong ito. Ngunit, sa pangkalahatan, ang buong punto ng nabuong code ay nagbibigay-daan ito sa amin na kopyahin sa Java ang "katotohanan" na ipinagkakaloob namin, alinman sa loob ng aming system o sa labas nito. Sa isang kahulugan, ito ang ginagawa ng mga compiler kapag bumubuo sila ng bytecode, machine code, o iba pang anyo ng source code - nakakakuha tayo ng representasyon ng ating "katotohanan" sa ibang wika, anuman ang mga partikular na dahilan.

Mayroong maraming mga naturang code generators. Halimbawa, Ang XJC ay maaaring bumuo ng Java code batay sa XSD o WSDL file. Ang prinsipyo ay palaging pareho:

  • Mayroong ilang katotohanan (panloob o panlabas) - halimbawa, isang detalye, isang modelo ng data, atbp.
  • Kailangan namin ng lokal na representasyon ng katotohanang ito sa aming programming language.

Bukod dito, halos palaging ipinapayong bumuo ng gayong representasyon upang maiwasan ang kalabisan.

Uri ng Mga Provider at Pagproseso ng Anotasyon

Tandaan: ang isa pa, mas moderno at partikular na diskarte sa pagbuo ng code para sa jOOQ ay ang paggamit ng mga provider ng uri, habang ipinapatupad ang mga ito sa F#. Sa kasong ito, ang code ay nabuo ng compiler, aktwal na nasa yugto ng compilation. Sa prinsipyo, ang naturang code ay hindi umiiral sa source form. Ang Java ay may katulad, kahit na hindi kasing elegante, mga tool - mga processor ng anotasyon, halimbawa, Lombok.

Sa isang kahulugan, ang parehong mga bagay ay nangyayari dito tulad ng sa unang kaso, maliban sa:

  • Hindi mo nakikita ang nabuong code (marahil ang sitwasyong ito ay tila hindi gaanong kasuklam-suklam sa isang tao?)
  • Dapat mong tiyakin na ang mga uri ay maaaring ibigay, ibig sabihin, ang "totoo" ay dapat palaging magagamit. Ito ay madali sa kaso ng Lombok, na nag-annotate ng "katotohanan". Ito ay medyo mas kumplikado sa mga modelo ng database na umaasa sa isang patuloy na magagamit na live na koneksyon.

Ano ang problema sa pagbuo ng code?

Bilang karagdagan sa nakakalito na tanong kung paano pinakamahusay na patakbuhin ang pagbuo ng code - manu-mano o awtomatiko, kailangan din nating banggitin na may mga taong naniniwala na ang pagbuo ng code ay hindi na kailangan. Ang katwiran para sa puntong ito ng pananaw, na madalas kong nakikita, ay mahirap mag-set up ng build pipeline. Oo, mahirap talaga. Lumilitaw ang mga karagdagang gastos sa imprastraktura. Kung nagsisimula ka pa lang sa isang partikular na produkto (maging ito man ay jOOQ, o JAXB, o Hibernate, atbp.), ang pagse-set up ng isang production environment ay nangangailangan ng oras na mas gugustuhin mong gugulin ang pag-aaral mismo ng API para makakuha ka ng halaga mula dito .

Kung ang mga gastos na nauugnay sa pag-unawa sa istraktura ng generator ay masyadong mataas, kung gayon, sa katunayan, ang API ay gumawa ng isang hindi magandang trabaho sa usability ng code generator (at sa paglaon ay lumalabas na ang pag-customize ng user dito ay kumplikado din). Ang kakayahang magamit ay dapat ang pinakamataas na priyoridad para sa anumang naturang API. Ngunit ito ay isa lamang argumento laban sa pagbuo ng code. Kung hindi, ganap na manu-mano ang pagsulat ng lokal na representasyon ng panloob o panlabas na katotohanan.

Marami ang magsasabi na wala silang panahon para gawin ang lahat ng ito. Nauubusan na sila ng mga deadline para sa kanilang Super Product. Balang araw aayusin natin ang mga conveyor ng assembly, magkakaroon tayo ng oras. Sasagutin ko sila:

Katotohanan muna, o kung bakit kailangang idisenyo ang isang sistema batay sa disenyo ng database
Orihinal, Alan O'Rourke, Audience Stack

Ngunit sa Hibernate/JPA napakadaling magsulat ng Java code.

Talaga. Para sa Hibernate at sa mga gumagamit nito, ito ay parehong pagpapala at isang sumpa. Sa Hibernate maaari kang magsulat lamang ng ilang entity, tulad nito:

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

At halos lahat ay handa na. Ngayon, nasa Hibernate na ang pagbuo ng kumplikadong "mga detalye" kung paano eksaktong tutukuyin ang entity na ito sa DDL ng iyong "dialect" ng 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);

... at simulan ang pagpapatakbo ng application. Isang napakagandang pagkakataon upang makapagsimula nang mabilis at sumubok ng iba't ibang bagay.

Gayunpaman, mangyaring payagan ako. nagsisinungaling ako.

  • Ipapatupad ba talaga ng Hibernate ang kahulugan nitong pinangalanang pangunahing key?
  • Gagawa ba ang Hibernate ng index sa TITLE? – Alam kong tiyak na kakailanganin natin ito.
  • Eksaktong gagawin ba ng Hibernate ang key na ito sa pagtukoy sa Identity Specification?

Hindi siguro. Kung binubuo mo ang iyong proyekto mula sa simula, palaging maginhawang itapon lamang ang lumang database at bumuo ng bago sa sandaling idagdag mo ang mga kinakailangang anotasyon. Kaya, ang entity ng Aklat sa huli ay kukuha ng anyo:

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

Malamig. Magbagong-buhay. Muli, sa kasong ito, magiging napakadali sa simula.

Ngunit kailangan mong bayaran ito mamaya

Maaga o huli kailangan mong pumunta sa produksyon. Iyan ay kapag ang modelong ito ay hihinto sa paggana. dahil:

Sa produksyon, hindi na posible, kung kinakailangan, na itapon ang lumang database at magsimula sa simula. Magiging legacy ang iyong database.

Mula ngayon at magpakailanman kailangan mong magsulat DDL migration script, halimbawa, gamit ang Flyway. Ano ang mangyayari sa iyong mga entity sa kasong ito? Maaari mong manu-manong iakma ang mga ito (at sa gayon ay doblehin ang iyong workload), o maaari mong sabihin sa Hibernate na i-regenerate ang mga ito para sa iyo (gaano ang posibilidad na ang mga ito ay nabuo sa ganitong paraan upang matugunan ang iyong mga inaasahan?) Sa alinmang paraan, matatalo ka.

Kaya sa sandaling makapasok ka sa produksyon kakailanganin mo ng mainit na mga patch. At kailangan nilang mailagay sa produksyon nang napakabilis. Dahil hindi ka naghanda at hindi nag-organisa ng maayos na pipeline ng iyong mga paglilipat para sa produksyon, ligaw mong itinapat ang lahat. At pagkatapos ay wala ka nang oras upang gawin ang lahat ng tama. At pinupuna mo ang Hibernate, dahil laging may kasalanan, hindi lang ikaw...

Sa halip, ang mga bagay ay maaaring ganap na naiiba mula sa simula. Halimbawa, maglagay ng mga bilog na gulong sa isang bisikleta.

Database muna

Ang tunay na "katotohanan" sa iyong database schema at ang "soberanya" sa ibabaw nito ay nasa loob ng database. Ang schema ay tinukoy lamang sa database mismo at wala saanman, at ang bawat kliyente ay may kopya ng schema na ito, kaya't makatuwirang ipatupad ang pagsunod sa schema at ang integridad nito, upang gawin ito nang tama sa database - kung saan ang impormasyon ay nakaimbak.
Ito ay luma, kahit na hackneyed na karunungan. Mahusay ang pangunahin at natatanging mga susi. Maganda ang mga foreign key. Ang pagsuri sa mga paghihigpit ay mabuti. Mga Pahayag - Ayos.

At saka, hindi lang iyon. Halimbawa, gamit ang Oracle, malamang na gusto mong tukuyin:

  • Saang tablespace matatagpuan ang iyong table?
  • Ano ang halaga ng PCTFREE nito?
  • Ano ang laki ng cache sa iyong sequence (sa likod ng id)

Maaaring hindi ito mahalaga sa maliliit na system, ngunit hindi mo kailangang maghintay hanggang sa lumipat ka sa larangan ng malaking dataβ€”maaari kang magsimulang makinabang mula sa mga pag-optimize ng storage na ibinigay ng vendor tulad ng mga nabanggit sa itaas nang mas maaga. Wala sa mga ORM na nakita ko (kabilang ang jOOQ) ang nagbibigay ng access sa buong hanay ng mga opsyon sa DDL na maaaring gusto mong gamitin sa iyong database. Nag-aalok ang mga ORM ng ilang tool na makakatulong sa iyong pagsulat ng DDL.

Ngunit sa pagtatapos ng araw, ang isang mahusay na dinisenyo na circuit ay nakasulat sa kamay sa DDL. Ang anumang nabuong DDL ay isang pagtatantya lamang nito.

Paano ang modelo ng kliyente?

Tulad ng nabanggit sa itaas, sa client kakailanganin mo ng kopya ng iyong database schema, ang client view. Hindi na kailangang banggitin, ang view ng kliyente na ito ay dapat na naka-sync sa aktwal na modelo. Ano ang pinakamahusay na paraan upang makamit ito? Gamit ang isang code generator.

Ang lahat ng mga database ay nagbibigay ng kanilang meta impormasyon sa pamamagitan ng SQL. Narito kung paano makuha ang lahat ng mga talahanayan mula sa iyong database sa iba't ibang mga diyalekto ng 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

Ang mga query na ito (o mga katulad, depende sa kung kailangan mo ring isaalang-alang ang mga view, materialized view, table-valued function) ay isinasagawa din sa pamamagitan ng pagtawag DatabaseMetaData.getTables() mula sa JDBC, o gamit ang jOOQ meta-module.

Mula sa mga resulta ng naturang mga query, medyo madaling makabuo ng anumang representasyon sa panig ng kliyente ng iyong modelo ng database, anuman ang teknolohiyang ginagamit mo sa kliyente.

  • Kung gumagamit ka ng JDBC o Spring, maaari kang lumikha ng isang set ng string constants
  • Kung gumagamit ka ng JPA, maaari mong buuin ang mga entity mismo
  • Kung gumagamit ka ng jOOQ, maaari kang bumuo ng jOOQ meta-modelo

Depende sa kung gaano karaming functionality ang inaalok ng iyong client API (hal. jOOQ o JPA), ang nabuong meta model ay maaaring maging talagang mayaman at kumpleto. Kunin, halimbawa, ang posibilidad ng implicit na pagsali, ipinakilala sa jOOQ 3.11, na umaasa sa nabuong meta impormasyon tungkol sa mga dayuhang pangunahing ugnayan na umiiral sa pagitan ng iyong mga talahanayan.

Ngayon ang anumang pagtaas ng database ay awtomatikong mag-a-update ng code ng kliyente. Isipin halimbawa:

ALTER TABLE book RENAME COLUMN title TO book_title;

Gusto mo ba talagang gawin ang trabahong ito ng dalawang beses? Sa anumang kaso. I-commit lang ang DDL, patakbuhin ito sa iyong build pipeline, at kunin ang na-update na entity:

@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 ang na-update na klase ng jOOQ. Karamihan sa mga pagbabago sa DDL ay nakakaapekto rin sa mga semantika, hindi lamang sa syntax. Samakatuwid, maaaring maging kapaki-pakinabang na tingnan ang pinagsama-samang code upang makita kung anong code ang (o maaaring) maaapektuhan ng iyong pagtaas ng database.

Ang tanging katotohanan

Anuman ang teknolohiyang ginagamit mo, palaging may isang modelo na ang tanging pinagmumulan ng katotohanan para sa ilang subsystem - o, sa pinakamababa, dapat nating pagsikapan ito at iwasan ang gayong kalituhan sa negosyo, kung saan ang "katotohanan" ay nasa lahat ng dako at wala nang sabay-sabay. . Ang lahat ay maaaring maging mas simple. Kung nakikipagpalitan ka lang ng mga XML file sa ibang system, gamitin lang ang XSD. Tingnan ang INFORMATION_SCHEMA meta-model mula sa jOOQ sa XML form:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • Ang XSD ay lubos na naiintindihan
  • Ang XSD ay nag-tokenize ng XML na nilalaman nang napakahusay at nagbibigay-daan sa pagpapatunay sa lahat ng mga wika ng kliyente
  • Ang XSD ay mahusay na bersyon at may advanced na backward compatibility
  • Maaaring isalin ang XSD sa Java code gamit ang XJC

Ang huling punto ay mahalaga. Kapag nakikipag-ugnayan sa isang panlabas na sistema gamit ang mga XML na mensahe, gusto naming makatiyak na wasto ang aming mga mensahe. Ito ay napakadaling makamit gamit ang JAXB, XJC at XSD. Nakakabaliw isipin na, gamit ang isang "Java first" na diskarte sa disenyo kung saan ginagawa namin ang aming mga mensahe bilang Java object, na kahit papaano ay maaaring magkakaugnay ang mga ito sa XML at ipadala sa ibang sistema para sa pagkonsumo. Ang XML na nabuo sa paraang ito ay magiging napakahina ng kalidad, hindi dokumentado, at mahirap bumuo. Kung mayroong service level agreement (SLA) para sa ganoong interface, agad naming sisirain ito.

Sa totoo lang, ito ang nangyayari sa lahat ng oras sa mga JSON API, ngunit iyon ay isa pang kuwento, mag-aaway ako sa susunod ...

Mga database: pareho sila

Kapag nagtatrabaho sa mga database, napagtanto mo na lahat sila ay halos magkapareho. Ang base ay nagmamay-ari ng data nito at dapat pamahalaan ang scheme. Ang anumang mga pagbabagong ginawa sa schema ay dapat na direktang ipatupad sa DDL upang ang nag-iisang pinagmulan ng katotohanan ay na-update.

Kapag may naganap na pag-update ng pinagmulan, dapat ding i-update ng lahat ng kliyente ang kanilang mga kopya ng modelo. Ang ilang mga kliyente ay maaaring isulat sa Java gamit ang jOOQ at Hibernate o JDBC (o pareho). Ang iba pang mga kliyente ay maaaring isulat sa Perl (nawa'y swertehin lang namin sila), habang ang iba ay maaaring isulat sa C#. Hindi mahalaga. Ang pangunahing modelo ay nasa database. Ang mga modelong nabuo gamit ang mga ORM ay karaniwang hindi maganda ang kalidad, hindi maganda ang pagkakadokumento, at mahirap i-develop.

Kaya huwag magkamali. Huwag magkamali sa simula pa lang. Magtrabaho mula sa database. Bumuo ng pipeline ng deployment na maaaring i-automate. Paganahin ang mga generator ng code upang gawing madaling kopyahin ang iyong modelo ng database at itapon ito sa mga kliyente. At itigil ang pag-aalala tungkol sa mga generator ng code. Magaling sila. Sa kanila ikaw ay magiging mas produktibo. Kailangan mo lang gumugol ng kaunting oras sa pag-set up ng mga ito mula pa sa simula - at pagkatapos ay naghihintay sa iyo ang mga taon ng pagtaas ng produktibo, na bubuo sa kasaysayan ng iyong proyekto.

Huwag mo pa akong pasalamatan, mamaya.

Linaw

Upang maging malinaw: Ang artikulong ito sa anumang paraan ay nagsusulong na kailangan mong ibaluktot ang buong system (ibig sabihin, domain, lohika ng negosyo, atbp., atbp.) upang magkasya sa iyong modelo ng database. Ang sinasabi ko sa artikulong ito ay ang code ng kliyente na nakikipag-ugnayan sa database ay dapat kumilos batay sa modelo ng database, nang sa gayon ay hindi nito kopyahin ang modelo ng database sa isang "first-class" na katayuan. Ang logic na ito ay karaniwang matatagpuan sa layer ng pag-access ng data sa iyong kliyente.

Sa dalawang antas na mga arkitektura, na napanatili pa rin sa ilang mga lugar, ang gayong modelo ng system ay maaaring ang tanging posible. Gayunpaman, sa karamihan ng mga system ang data access layer ay tila sa akin ay isang "subsystem" na sumasaklaw sa modelo ng database.

Mga pagbubukod

May mga pagbubukod sa bawat panuntunan, at nasabi ko na na ang database-first at source-code generation approach ay maaaring minsan ay hindi naaangkop. Narito ang ilang mga pagbubukod (marahil ay may iba pa):

  • Kapag hindi alam ang schema at kailangang matuklasan. Halimbawa, isa kang provider ng tool na tumutulong sa mga user na mag-navigate sa anumang diagram. Ugh. Walang code generation dito. Ngunit gayon pa man, ang database ay nauuna.
  • Kapag ang isang circuit ay dapat na binuo sa mabilisang upang malutas ang ilang mga problema. Ang halimbawang ito ay tila isang bahagyang imahinatibong bersyon ng pattern halaga ng katangian ng entity, ibig sabihin, wala ka talagang malinaw na tinukoy na scheme. Sa kasong ito, madalas ay hindi ka makatitiyak na babagay sa iyo ang isang RDBMS.

Ang mga pagbubukod ay likas na katangi-tangi. Sa karamihan ng mga kaso na kinasasangkutan ng paggamit ng isang RDBMS, ang schema ay alam nang maaga, ito ay namamalagi sa loob ng RDBMS at ang tanging pinagmumulan ng "katotohanan", at ang lahat ng mga kliyente ay kailangang kumuha ng mga kopya na nagmula rito. Sa isip, kailangan mong gumamit ng code generator.

Pinagmulan: www.habr.com

Magdagdag ng komento