Vispirms patiesība jeb kāpēc sistēma ir jāprojektē, pamatojoties uz datu bāzes struktūru

Čau Habr!

Mēs turpinām izpētÄ«t tēmu Java Šø pavasaristostarp datu bāzes lÄ«menÄ«. Å odien piedāvājam lasÄ«t par to, kāpēc, veidojot lielas lietojumprogrammas, noteicoŔā nozÄ«me ir datu bāzes struktÅ«rai, nevis Java kodam, kā tas tiek darÄ«ts un kādi ir Ŕī noteikuma izņēmumi.

Å ajā diezgan novēlotajā rakstā es paskaidroÅ”u, kāpēc, manuprāt, gandrÄ«z visos gadÄ«jumos datu modelis lietojumprogrammā ir jāveido "no datu bāzes", nevis "no Java iespējām" (vai neatkarÄ«gi no klienta valodas strādājot ar). Izvēloties otro pieeju, jÅ«s ieejat garā sāpju un cieÅ”anu ceļā, tiklÄ«dz jÅ«su projekts sāk augt.

Raksts tika uzrakstīts, pamatojoties uz viens jautājums, kas norādīts Stack Overflow.

Interesantas diskusijas par reddit sadaļās /r/java Šø /r/programmÄ“Å”ana.

Kodu ģenerēŔana

Cik es esmu pārsteigts, ka ir tik mazs lietotāju slānis, kas, iepazinuÅ”ies ar jOOQ, aizvainojas par to, ka jOOQ nopietni paļaujas uz pirmkoda Ä£enerÄ“Å”anu. Neviens neliedz jums izmantot jOOQ tā, kā jÅ«s uzskatāt par pareizu, un neviens neliek jums izmantot kodu Ä£enerÄ“Å”anu. Bet pēc noklusējuma (kā aprakstÄ«ts rokasgrāmatā) jOOQ darbojas Ŕādi: jÅ«s sākat ar (mantoto) datu bāzes shēmu, apgriežat to ar jOOQ koda Ä£eneratoru, lai iegÅ«tu klaÅ”u kopu, kas attēlo jÅ«su tabulas, un pēc tam ierakstiet tipa- droÅ”i vaicājumi Å”ajās tabulās:

	for (Record2<String, String> record : DSL.using(configuration)
//   ^^^^^^^^^^^^^^^^^^^^^^^ Š˜Š½Ń„Š¾Ń€Š¼Š°Ń†Šøя Š¾ тŠøŠæŠ°Ń… Š²Ń‹Š²ŠµŠ“ŠµŠ½Š° Š½Š° 
//   Š¾ŃŠ½Š¾Š²Š°Š½ŠøŠø сŠ³ŠµŠ½ŠµŃ€ŠøрŠ¾Š²Š°Š½Š½Š¾Š³Š¾ ŠŗŠ¾Š“Š°, Š½Š° ŠŗŠ¾Ń‚Š¾Ń€Ń‹Š¹ ссыŠ»Š°ŠµŃ‚ся ŠæрŠøŠ²ŠµŠ“ŠµŠ½Š½Š¾Šµ
// Š½ŠøŠ¶Šµ усŠ»Š¾Š²ŠøŠµ SELECT 
 
       .select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
//           vvvvv ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^ сŠ³ŠµŠ½ŠµŃ€ŠøрŠ¾Š²Š°Š½Š½Ń‹Šµ ŠøŠ¼ŠµŠ½Š°
       .from(ACTOR)
       .orderBy(1, 2)) {
    // ...
}

Kods tiek Ä£enerēts manuāli ārpus bÅ«vējuma vai manuāli katrā bÅ«vniecÄ«bā. Piemēram, Ŕāda reÄ£enerācija var sekot tÅ«lÄ«t pēc tam Flyway datu bāzes migrācija, ko var veikt arÄ« manuāli vai automātiski.

Avota koda ģenerēŔana

Ir dažādas filozofijas, priekÅ”rocÄ«bas un trÅ«kumi, kas saistÄ«ti ar Ŕīm pieejām koda Ä£enerÄ“Å”anai - manuālai un automātiskai -, kuras es Å”ajā rakstā sÄ«kāk neapspriedÄ«Å”u. Bet kopumā visa Ä£enerētā koda bÅ«tÄ«ba ir tāda, ka tas ļauj Java reproducēt ā€œpatiesÄ«buā€, ko mēs uzskatām par paÅ”saprotamu gan mÅ«su sistēmā, gan ārpus tās. Savā ziņā kompilatori, kas Ä£enerē baitkodu, maŔīnkodu vai cita veida kodu no pirmkoda, dara to paÅ”u ā€“ mēs iegÅ«stam savas "patiesÄ«bas" attēlojumu citā valodā, neatkarÄ«gi no konkrētiem iemesliem.

Ir daudz Ŕādu kodu Ä£eneratoru. Piemēram, XJC var Ä£enerēt Java kodu, pamatojoties uz XSD vai WSDL failiem. Princips vienmēr ir vienāds:

  • Ir kāda patiesÄ«ba (iekŔēja vai ārēja) - piemēram, specifikācija, datu modelis utt.
  • Mums ir nepiecieÅ”ams vietējs Ŕīs patiesÄ«bas attēlojums mÅ«su programmÄ“Å”anas valodā.

Turklāt gandrÄ«z vienmēr ir ieteicams Ä£enerēt Ŕādu attēlojumu, lai izvairÄ«tos no dublÄ“Å”anas.

Tipa nodroŔinātāji un anotāciju apstrāde

PiezÄ«me: Cita, modernāka un specifiskāka pieeja koda Ä£enerÄ“Å”anai jOOQ ietver tipu nodroÅ”inātāju izmantoÅ”anu, kā tie ir ieviesti F#. Å ajā gadÄ«jumā kodu Ä£enerē kompilators, faktiski kompilācijas stadijā. Principā Ŕāds kods neeksistē pirmkodu veidā. Java ir lÄ«dzÄ«gi, lai arÄ« ne tik eleganti rÄ«ki - tie ir, piemēram, anotāciju procesori, Lomboka.

Zināmā nozÄ«mē Å”eit notiek tās paÅ”as lietas, kas pirmajā gadÄ«jumā, izņemot:

  • JÅ«s neredzat Ä£enerēto kodu (varbÅ«t Ŕī situācija kādam neŔķiet tik riebÄ«ga?)
  • Jums ir jānodroÅ”ina, ka var norādÄ«t veidus, tas ir, vienmēr ir jābÅ«t pieejamam ā€œtrueā€. Tas ir viegli Lombok gadÄ«jumā, kas anotē "patiesÄ«bu". Tas ir nedaudz sarežģītāk ar datu bāzes modeļiem, kas ir atkarÄ«gi no tieŔā savienojuma, kas vienmēr ir pieejams.

Kāda ir koda Ä£enerÄ“Å”anas problēma?

Papildus kutelÄ«gajam jautājumam, kā labāk sākt koda Ä£enerÄ“Å”anu - manuāli vai automātiski, jāpiemin, ka ir cilvēki, kas uzskata, ka koda Ä£enerÄ“Å”ana nemaz nav vajadzÄ«ga. Pamatojums Å”im viedoklim, ar kuru es saskāros visbiežāk, ir tas, ka tad ir grÅ«ti izveidot bÅ«vniecÄ«bas cauruļvadu. Jā, tas tieŔām ir grÅ«ti. Ir papildu infrastruktÅ«ras izmaksas. Ja jÅ«s tikko sākat darbu ar kādu konkrētu produktu (vai tas bÅ«tu jOOQ, JAXB, vai hibernate utt.), ir nepiecieÅ”ams laiks, lai iestatÄ«tu darbgaldu, ko vēlaties pavadÄ«t, apgÅ«stot paÅ”u API, lai no tā gÅ«tu labumu. .

Ja izmaksas, kas saistÄ«tas ar Ä£eneratora ierÄ«ces izpratni, ir pārāk augstas, tad API patieŔām slikti strādāja pie koda Ä£eneratora lietojamÄ«bas (un nākotnē izrādÄ«sies, ka arÄ« pielāgoÅ”ana tajā ir sarežģīta). Jebkurai Ŕādai API augstākajai prioritātei ir jābÅ«t lietojamÄ«bai. Bet tas ir tikai viens arguments pret koda Ä£enerÄ“Å”anu. Pretējā gadÄ«jumā pilnÄ«bā ar roku uzrakstiet vietējās vai ārējās patiesÄ«bas attēlojumu.

Daudzi teiks, ka viņiem nav laika to visu darÄ«t. Viņiem ir beidzies superprodukta termiņŔ. Kādreiz vēlāk Ä·emmēsim montāžas konveijerus, bÅ«s laiks. Es viņiem atbildÄ“Å”u:

Vispirms patiesība jeb kāpēc sistēma ir jāprojektē, pamatojoties uz datu bāzes struktūru
Oriģināls, Alans O'Rurks, Audience Stack

Bet Hibernate / JPA ir tik vienkārŔi rakstīt kodu "Java".

TieŔām. Hibernate un tā lietotājiem tas ir gan svētÄ«gs, gan lāsts. Hibernācijas režīmā varat vienkārÅ”i ierakstÄ«t pāris entÄ«tijas, piemēram:

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

Un gandrÄ«z viss ir gatavs. Tagad Hibernate galvenais uzdevums ir Ä£enerēt sarežģītas ā€œdetaļasā€ par to, kā tieÅ”i Ŕī entÄ«tija tiks definēta jÅ«su SQL ā€œdialektaā€ DDL:

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

... un sāciet palaist lietojumprogrammu. Ä»oti forÅ”a funkcija, lai ātri sāktu darbu un izmēģinātu dažādas lietas.

Tomēr ļaujiet man. Es meloju.

  • Vai Hibernate patieŔām ieviesÄ«s Ŕīs nosauktās primārās atslēgas definÄ«ciju?
  • Vai Hibernate izveidos indeksu TITLE? Es noteikti zinu, ka mums tas ir vajadzÄ«gs.
  • Vai Hibernate padarÄ«s Å”o atslēgu par identitātes atslēgu identitātes specifikācijā?

Visticamāk ne. Ja jÅ«s izstrādājat savu projektu no nulles, vienmēr ir ērti vienkārÅ”i izmest veco datu bāzi un izveidot jaunu, tiklÄ«dz esat pievienojis nepiecieÅ”amās anotācijas. Tātad grāmatas entÄ«tijai galu galā bÅ«s Ŕāda forma:

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

ForŔi. Atjaunot. Arī Ŕajā gadījumā sākumā tas būs ļoti viegli.

Bet vēlāk par to būs jāmaksā.

Agrāk vai vēlāk jums bÅ«s jāiet uz ražoÅ”anu. TieÅ”i tad modelis pārstāj darboties. Jo:

RažoÅ”anā vairs nebÅ«s iespējams nepiecieÅ”amÄ«bas gadÄ«jumā izmest veco datubāzi un sākt visu no nulles. JÅ«su datu bāze kļūs par mantotu.

No Ŕī brīža un uz visiem laikiem jums bÅ«s jāraksta DDL migrācijas skripti, piemēram, izmantojot Flyway. Un kas Å”ajā gadÄ«jumā notiks ar jÅ«su vienÄ«bām? Varat tos pielāgot manuāli (un dubultot darba slodzi), vai arÄ« Hibernate tos atjaunot (cik iespējams, ka Ŕādi Ä£enerētais atbilst jÅ«su vēlmēm?) JÅ«s zaudējat jebkurā gadÄ«jumā.

Tādējādi, tiklÄ«dz pāriesit uz ražoÅ”anu, jums bÅ«s nepiecieÅ”ami karstie ielāpi. Un tie ir ļoti ātri jānogādā ražoÅ”anā. Tā kā jÅ«s neesat sagatavojis un organizējis vienmērÄ«gu migrÄ“Å”anu ražoÅ”anai, jÅ«s neprātÄ«gi veicat ielāpu. Un tad jums nav laika darÄ«t visu pareizi. Un jÅ«s rājat Hibernate, jo tajā vienmēr ir vainÄ«gs kāds, bet ne jÅ«s ...

Tā vietā jau no paÅ”a sākuma visu varēja darÄ«t pavisam savādāk. Piemēram, uzvelciet velosipēdam apaļus riteņus.

Vispirms datu bāze

ÄŖstā "patiesÄ«ba" jÅ«su datu bāzes shēmā un "suverenitāte" pār to slēpjas datu bāzē. Shēma ir definēta tikai paŔā datu bāzē un nekur citur, un katram klientam ir Ŕīs shēmas kopija, tāpēc ir pilnÄ«gi saprātÄ«gi nodroÅ”ināt shēmas ievēroÅ”anu un tās integritāti, lai to izdarÄ«tu tieÅ”i datu bāzē - kur informācija tiek glabāta.
Tā ir veca, pat sagrauta gudrÄ«ba. Primārās un unikālās atslēgas ir labas. Ar sveŔām atslēgām viss kārtÄ«bā. Ierobežojumu pārbaude ir laba. Apgalvojumi - Labi.

Un tas vēl nav viss. Piemēram, izmantojot Oracle, iespējams, vēlēsities norādīt:

  • Kādā galda vietā atrodas jÅ«su galds
  • Kāda ir viņas PCTFREE vērtÄ«ba
  • Kāds ir keÅ”atmiņas lielums jÅ«su secÄ«bā (aiz id)

Mazās sistēmās tam visam var nebÅ«t nozÄ«mes, taču nav jāgaida lÄ«dz pārejai uz "lielo datu" sfēru ā€“ jÅ«s varat sākt gÅ«t labumu no pārdevēja nodroÅ”inātajām krātuves optimizācijām, piemēram, iepriekÅ”minētajām, daudz agrāk. Neviens no ORM, ko esmu redzējis (tostarp jOOQ), nenodroÅ”ina piekļuvi visam DDL opciju komplektam, ko jÅ«s varētu vēlēties izmantot savā datu bāzē. ORM piedāvā dažus rÄ«kus, kas palÄ«dz rakstÄ«t DDL.

Bet dienas beigās labi izstrādāta shēma tiek ar roku rakstÄ«ta DDL. JebkurÅ” Ä£enerētais DDL ir tikai tā aptuvens rādÄ«tājs.

Kā ar klienta modeli?

Kā minēts iepriekÅ”, klientam bÅ«s nepiecieÅ”ama datu bāzes shēmas kopija, klienta skats. Lieki piebilst, ka Å”im klienta skatam ir jābÅ«t sinhronizētam ar reālo modeli. Kāds ir labākais veids, kā to panākt? Ar kodu Ä£eneratoru.

Visas datu bāzes sniedz savu metainformāciju, izmantojot SQL. Tālāk ir norādīts, kā no savas datu bāzes iegūt visas tabulas dažādos SQL dialektos.

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

Šie vaicājumi (vai līdzīgi, atkarībā no tā, vai jāņem vērā arī skati, materializētie skati, tabulas vērtības) tiek izpildīti arī izsaucot DatabaseMetaData.getTables() no JDBC vai izmantojot jOOQ meta moduli.

No Ŕādu vaicājumu rezultātiem ir samērā viegli Ä£enerēt jebkuru jÅ«su datu bāzes modeļa klienta puses attēlojumu neatkarÄ«gi no klienta izmantotās tehnoloÄ£ijas.

  • Ja izmantojat JDBC vai Spring, varat izveidot virknes konstantu kopu
  • Ja izmantojat JPA, varat Ä£enerēt paÅ”as entÄ«tijas
  • Ja izmantojat jOOQ, varat Ä£enerēt jOOQ meta modeli

AtkarÄ«bā no tā, cik daudz iespēju piedāvā jÅ«su klienta API (piemēram, jOOQ vai JPA), Ä£enerētais meta modelis var bÅ«t patieŔām bagāts un pilnÄ«gs. Ņemiet, piemēram, netieÅ”as pievienoÅ”anās iespēju, ieviests jOOQ 3.11, kas balstās uz Ä£enerētu metainformāciju par ārējo atslēgu attiecÄ«bām starp jÅ«su tabulām.

Tagad jebkurÅ” datu bāzes pieaugums automātiski atjauninās klienta kodu. Iedomājieties, piemēram:

ALTER TABLE book RENAME COLUMN title TO book_title;

Vai tieŔām vēlaties Å”o darbu darÄ«t divas reizes? Nekādā gadÄ«jumā. Mēs vienkārÅ”i veicam DDL, palaižam to caur jÅ«su bÅ«vÄ“Å”anas cauruļvadu un iegÅ«stam atjaunināto entÄ«tiju:

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

Vai arÄ« atjauninātā jOOQ klase. Lielākā daļa DDL izmaiņu ietekmē arÄ« semantiku, ne tikai sintaksi. Tāpēc var bÅ«t ērti apkopotajā kodā redzēt, kādu kodu ietekmēs (vai varētu) ietekmēt datu bāzes palielināŔana.

Vienīgā patiesība

NeatkarÄ«gi no tā, kuru tehnoloÄ£iju izmantojat, vienmēr ir viens modelis, kas ir vienÄ«gais patiesÄ«bas avots kādai apakÅ”sistēmai ā€“ vai vismaz mums vajadzētu uz to censties un izvairÄ«ties no uzņēmuma neskaidrÄ«bām, kur "patiesÄ«ba" ir visur un nekur uzreiz. Viss var bÅ«t daudz vieglāk. Ja jÅ«s vienkārÅ”i apmaināties ar XML failiem ar kādu citu sistēmu, vienkārÅ”i izmantojiet XSD. Apskatiet jOOQ INFORMATION_SCHEMA metamodeli XML formātā:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • XSD ir labi saprotams
  • XSD ļoti labi iezÄ«mē XML saturu un ļauj pārbaudÄ«t visās klientu valodās
  • XSD ir labi izstrādāta versija un ļoti saderÄ«ga ar atpakaļejoÅ”u datumu
  • XSD var pārtulkot Java kodā, izmantojot XJC

Pēdējais punkts ir svarÄ«gs. Sazinoties ar ārēju sistēmu, izmantojot XML ziņojumus, mēs vēlamies bÅ«t pārliecināti, ka mÅ«su ziņojumi ir derÄ«gi. To ir ļoti viegli panākt ar JAXB, XJC un XSD. BÅ«tu ārprāts domāt, ka Java-first dizaina pieejā, kurā mēs veidojam savus ziņojumus kā Java objektus, tos kaut kādā veidā varētu atveidot saprotami XML formātā un nosÅ«tÄ«t patēriņam uz citu sistēmu. Šādā veidā Ä£enerētais XML bÅ«tu ļoti sliktas kvalitātes, nedokumentēts un grÅ«ti izstrādājams. Ja bÅ«tu vienoÅ”anās par servisa kvalitātes lÄ«meni (SLA) par Ŕādu interfeisu, mēs to uzreiz saskrÅ«vētu.

GodÄ«gi sakot, tas ir tieÅ”i tas, kas visu laiku notiek ar JSON API, bet tas ir cits stāsts, es strÄ«dÄ“Å”os nākamreiz...

Datu bāzes: tās ir vienādas

Strādājot ar datu bāzēm, jÅ«s saprotat, ka tās visas bÅ«tÄ«bā ir vienādas. Datu bāzei pieder tās dati, un tai ir jāpārvalda shēma. Visas shēmā veiktās modifikācijas ir jāievieÅ” tieÅ”i DDL, lai tiktu atjaunināts vienÄ«gais patiesÄ«bas avots.

Kad avota atjauninājums ir noticis, visiem klientiem ir jāatjaunina arī savas modeļa kopijas. Daži klienti var būt rakstīti Java, izmantojot jOOQ un Hibernate vai JDBC (vai abus). Citi klienti var būt rakstīti Perl valodā (vēlēsim viņiem veiksmi), citi C#. Tas nav svarīgi. Galvenais modelis ir datu bāzē. ORM ģenerētie modeļi parasti ir sliktas kvalitātes, slikti dokumentēti un grūti izstrādājami.

Tāpēc nepieļaujiet kļūdas. Nepieļaujiet kļūdas no paÅ”a sākuma. Darbs no datu bāzes. Izveidojiet izvietoÅ”anas cauruļvadu, ko var automatizēt. Iespējojiet kodu Ä£eneratorus, lai ērti kopētu datu bāzes modeli un izmestu to klientiem. Un beidziet uztraukties par kodu Ä£eneratoriem. Viņi ir labi. Ar tiem jÅ«s kļūsit produktÄ«vāks. Viss, kas jums jādara, ir jāpavada nedaudz laika to iestatÄ«Å”anai jau no paÅ”a sākuma, un jÅ«s iegÅ«sit gadiem ilgi uzlabotu veiktspēju, lai izveidotu sava projekta stāstu.

Vēlāk nesaki man paldies.

NoskaidroŔana

SkaidrÄ«bas labad: Å”is raksts nekādā veidā neatbalsta, ka visa sistēma (t.i., domēns, biznesa loÄ£ika utt. utt.) ir jāpielāgo, lai tā atbilstu jÅ«su datu bāzes modelim. Å ajā rakstā es runāju par to, ka klienta kodam, kas mijiedarbojas ar datu bāzi, jādarbojas, pamatojoties uz datu bāzes modeli, lai tas neatveidotu datu bāzes modeli "pirmās klases" statusā. Šāda loÄ£ika parasti atrodas jÅ«su klienta datu piekļuves slānÄ«.

DivlÄ«meņu arhitektÅ«rās, kas vietām joprojām ir saglabājuŔās, Ŕāds sistēmas modelis var bÅ«t vienÄ«gais iespējamais. Tomēr lielākajā daļā sistēmu man Ŕķiet, ka datu piekļuves slānis ir "apakÅ”sistēma", kas iekapsulē datu bāzes modeli.

Izņēmumi

Katram noteikumam ir izņēmumi, un es jau iepriekÅ” teicu, ka datu bāzes pirmā un avota koda Ä£enerÄ“Å”anas pieeja dažreiz var bÅ«t nepiemērota. Å eit ir daži Ŕādi izņēmumi (iespējams, ir arÄ« citi):

  • Ja shēma nav zināma un ir jāatver. Piemēram, jÅ«s sniedzat rÄ«ku, kas palÄ«dz lietotājiem orientēties jebkurā diagrammā. FÅ«. Å eit nav koda Ä£enerÄ“Å”anas. Bet tomēr - datubāze pirmām kārtām.
  • Kad ķēde ir jāģenerē lidojumā, lai atrisinātu kādu problēmu. Å Ä·iet, ka Å”is piemērs ir nedaudz raiba modeļa versija entÄ«tijas atribÅ«ta vērtÄ«ba, t.i., jums nav Ä«sti definētas shēmas. Å ajā gadÄ«jumā jÅ«s bieži pat nevarat bÅ«t pārliecināts, ka RDBMS jums bÅ«s piemērots.

Izņēmumi pēc bÅ«tÄ«bas ir ārkārtēji. Vairumā gadÄ«jumu, kas saistÄ«ti ar RDBMS izmantoÅ”anu, shēma ir iepriekÅ” zināma, tā atrodas RDBMS un ir vienÄ«gais "patiesÄ«bas" avots, un visiem klientiem ir jāiegādājas no tās iegÅ«tās kopijas. Ideālā gadÄ«jumā tam vajadzētu bÅ«t koda Ä£eneratoram.

Avots: www.habr.com

Pievieno komentāru