Kebenaran dahulu, atau mengapa sistem perlu direka bentuk berdasarkan struktur pangkalan data

Hai Habr!

Kami terus meneroka topik tersebut Java ΠΈ Springtermasuk di peringkat pangkalan data. Hari ini kami menawarkan untuk membaca tentang mengapa, apabila mereka bentuk aplikasi besar, struktur pangkalan data, dan bukan kod Java, yang harus menjadi kepentingan yang menentukan, bagaimana ini dilakukan, dan apakah pengecualian kepada peraturan ini.

Dalam artikel yang agak terlambat ini, saya akan menerangkan mengapa saya berpendapat bahawa dalam hampir semua kes, model data dalam aplikasi harus direka "dari pangkalan data" dan bukannya "daripada keupayaan Java" (atau apa sahaja bahasa klien yang anda gunakan. berkerja dengan). Dengan memilih pendekatan kedua, anda memasuki laluan kesakitan dan penderitaan yang panjang apabila projek anda mula berkembang.

Artikel itu ditulis berdasarkan satu soalan, diberikan pada Stack Overflow.

Perbincangan menarik tentang reddit dalam bahagian /r/java ΠΈ /r/pengaturcaraan.

Penjanaan kod

Betapa terkejutnya saya kerana terdapat lapisan pengguna yang begitu kecil yang, setelah mengenali jOOQ, membenci hakikat bahawa jOOQ sangat bergantung pada penjanaan kod sumber untuk dijalankan. Tiada siapa yang menghalang anda daripada menggunakan jOOQ mengikut cara yang anda lihat sesuai, dan tiada siapa yang memaksa anda menggunakan penjanaan kod. Tetapi secara lalai (seperti yang diterangkan dalam manual), jOOQ berfungsi seperti ini: anda bermula dengan skema pangkalan data (warisan), merekayasa terbalik dengan penjana kod jOOQ untuk mendapatkan satu set kelas yang mewakili jadual anda, dan kemudian tulis jenis- pertanyaan selamat terhadap jadual ini:

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

Kod dijana sama ada secara manual di luar binaan, atau secara manual pada setiap binaan. Contohnya, penjanaan semula sebegitu mungkin menyusul serta-merta selepas itu Penghijrahan pangkalan data Flyway, yang juga boleh dilakukan secara manual atau automatik.

Penjanaan kod sumber

Terdapat pelbagai falsafah, kebaikan dan keburukan yang dikaitkan dengan pendekatan penjanaan kod ini - manual dan automatik - yang saya tidak akan membincangkan secara terperinci dalam artikel ini. Tetapi, secara umum, keseluruhan kod yang dijana ialah ia membolehkan anda menghasilkan semula dalam Java "kebenaran" yang kami ambil mudah, sama ada dalam sistem kami atau di luarnya. Dari satu segi, pengkompil yang menjana kod bait, kod mesin atau beberapa jenis kod lain daripada kod sumber melakukan perkara yang sama - kita mendapat gambaran "kebenaran" kita dalam bahasa lain, tanpa mengira sebab tertentu.

Terdapat banyak penjana kod sedemikian. Sebagai contoh, XJC boleh menjana kod Java berdasarkan fail XSD atau WSDL. Prinsipnya sentiasa sama:

  • Terdapat beberapa kebenaran (dalaman atau luaran) - contohnya, spesifikasi, model data, dsb.
  • Kami memerlukan perwakilan tempatan kebenaran ini dalam bahasa pengaturcaraan kami.

Lebih-lebih lagi, hampir selalu dinasihatkan untuk menjana perwakilan sedemikian - untuk mengelakkan lebihan.

Pembekal Jenis dan Pemprosesan Anotasi

Nota: Satu lagi pendekatan yang lebih moden dan khusus untuk penjanaan kod untuk jOOQ melibatkan penggunaan pembekal jenis, kerana ia dilaksanakan dalam F#. Dalam kes ini, kod dijana oleh pengkompil, sebenarnya pada peringkat penyusunan. Pada dasarnya, kod tersebut tidak wujud dalam bentuk kod sumber. Di Java, terdapat alat yang serupa, walaupun tidak begitu elegan - ini adalah pemproses anotasi, contohnya, Lombok.

Dalam erti kata tertentu, perkara yang sama berlaku di sini seperti dalam kes pertama, kecuali:

  • Anda tidak melihat kod yang dijana (mungkin keadaan ini tidak kelihatan begitu menjijikkan kepada seseorang?)
  • Anda mesti memastikan bahawa jenis boleh disediakan, iaitu, "benar" mesti sentiasa tersedia. Ini mudah dalam kes Lombok, yang memberi penjelasan "kebenaran". Ia sedikit lebih sukar dengan model pangkalan data yang bergantung pada sambungan langsung yang sentiasa tersedia.

Apakah masalah dengan penjanaan kod?

Sebagai tambahan kepada persoalan rumit tentang bagaimana lebih baik untuk memulakan penjanaan kod - secara manual atau automatik, saya perlu menyebut bahawa terdapat orang yang percaya bahawa penjanaan kod tidak diperlukan sama sekali. Justifikasi untuk sudut pandangan ini, yang paling kerap saya temui, ialah sukar untuk menyediakan saluran paip binaan. Ya, memang susah. Terdapat kos infrastruktur tambahan. Jika anda baru bermula dengan produk tertentu (sama ada jOOQ, atau JAXB, atau Hibernate, dsb.), memerlukan masa untuk menyediakan meja kerja yang anda ingin gunakan untuk mempelajari API itu sendiri untuk mendapatkan nilai daripadanya .

Sekiranya kos yang berkaitan dengan memahami peranti penjana adalah terlalu tinggi, maka, sesungguhnya, API melakukan kerja yang buruk pada kebolehgunaan penjana kod (dan pada masa akan datang ternyata penyesuaian di dalamnya juga sukar). Kebolehgunaan harus menjadi keutamaan tertinggi untuk mana-mana API tersebut. Tetapi itu hanya satu hujah terhadap penjanaan kod. Jika tidak, tulis sepenuhnya dengan tangan perwakilan tempatan kebenaran dalaman atau luaran.

Ramai yang akan mengatakan bahawa mereka tidak mempunyai masa untuk melakukan semua ini. Mereka berada pada tarikh akhir untuk Produk Super mereka. Suatu hari nanti kita akan menyikat penghantar pemasangan, kita akan mempunyai masa. Saya akan menjawab mereka:

Kebenaran dahulu, atau mengapa sistem perlu direka bentuk berdasarkan struktur pangkalan data
Asal, Alan O'Rourke, Timbunan Khalayak

Tetapi dalam Hibernate / JPA sangat mudah untuk menulis kod "dalam Java".

sungguh. Bagi Hibernate dan penggunanya, ini adalah rahmat dan kutukan. Dalam Hibernate, anda hanya boleh menulis beberapa entiti, seperti ini:

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

Dan hampir semuanya sudah siap. Sekarang banyak Hibernate adalah untuk menjana "butiran" kompleks tentang bagaimana sebenarnya entiti ini akan ditakrifkan dalam DDL "dialek" SQL anda:

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

... dan mula menjalankan aplikasi. Ciri yang sangat hebat untuk bangun dan berjalan dengan pantas dan mencuba pelbagai perkara.

Namun, biarlah saya. Saya berbohong.

  • Adakah Hibernate benar-benar menguatkuasakan takrif kunci utama yang dinamakan ini?
  • Adakah Hibernate akan mencipta indeks pada TITLE? Saya tahu pasti kita memerlukannya.
  • Adakah Hibernate akan menjadikan kunci ini sebagai kunci identiti dalam Spesifikasi Identiti?

Mungkin tidak. Jika anda membangunkan projek anda dari awal, ia sentiasa mudah untuk membuang pangkalan data lama dan menjana yang baharu sebaik sahaja anda menambah anotasi yang diperlukan. Jadi, entiti Buku akhirnya akan mengambil bentuk:

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

Sejuk. Menjana semula. Sekali lagi, dalam kes ini, ia akan menjadi sangat mudah pada permulaannya.

Tetapi anda perlu membayarnya kemudian.

Lambat-laun anda perlu masuk ke dalam pengeluaran. Itulah apabila model berhenti berfungsi. Kerana:

Dalam pengeluaran, tidak mungkin lagi, jika perlu, untuk membuang pangkalan data lama dan memulakan segala-galanya dari awal. Pangkalan data anda akan bertukar menjadi pangkalan data lama.

Mulai sekarang dan selamanya anda perlu menulis Skrip pemindahan DDL, cth. menggunakan Flyway. Dan apakah yang akan berlaku kepada entiti anda dalam kes ini? Anda boleh sama ada menyesuaikannya secara manual (dan menggandakan beban kerja anda) atau meminta Hibernate menjana semula mereka untuk anda (berkemungkinan besar yang dijana dengan cara ini untuk memenuhi jangkaan anda?) Anda akan kalah dalam apa jua cara.

Oleh itu, sebaik sahaja anda bergerak ke dalam pengeluaran, anda memerlukan tampalan panas. Dan mereka perlu dibawa ke pengeluaran dengan cepat. Memandangkan anda belum menyediakan dan mengatur saluran paip yang lancar bagi penghijrahan anda untuk pengeluaran, anda menampal secara liar. Dan kemudian anda tidak mempunyai masa untuk melakukan semuanya dengan betul. Dan anda memarahi Hibernate, kerana ia sentiasa salah sesiapa, tetapi bukan anda ...

Sebaliknya, dari awal lagi, semuanya boleh dilakukan dengan cara yang berbeza. Contohnya, letakkan roda bulat pada basikal.

Pangkalan data dahulu

"Kebenaran" sebenar dalam skema pangkalan data anda dan "kedaulatan" ke atasnya terletak dalam pangkalan data. Skema ditakrifkan hanya dalam pangkalan data itu sendiri dan tidak di tempat lain, dan setiap pelanggan mempunyai salinan skema ini, jadi sangat masuk akal untuk menguatkuasakan pematuhan kepada skema dan integritinya, untuk melakukannya dengan betul dalam pangkalan data - di mana maklumat disimpan.
Ini adalah kebijaksanaan lama walaupun digodam. Kunci utama dan unik adalah bagus. Kunci asing baik-baik saja. Pemeriksaan kekangan adalah baik. Ketegasan - Baiklah.

Dan, bukan itu sahaja. Sebagai contoh, menggunakan Oracle, anda mungkin ingin menentukan:

  • Dalam ruang meja manakah meja anda
  • Apakah nilai PCTFREE dia
  • Apakah saiz cache dalam jujukan anda (di belakang id)

Semua ini mungkin tidak penting dalam sistem kecil, tetapi tidak perlu menunggu sehingga peralihan ke alam "data besar" - anda boleh mula mendapat manfaat daripada pengoptimuman storan yang disediakan oleh vendor, seperti yang dinyatakan di atas, lebih awal lagi. Tiada satu pun ORM yang saya lihat (termasuk jOOQ) memberikan akses kepada set penuh pilihan DDL yang mungkin anda mahu gunakan dalam pangkalan data anda. ORM menawarkan beberapa alatan untuk membantu anda menulis DDL.

Tetapi pada penghujung hari, skema yang direka dengan baik ditulis tangan dalam DDL. Sebarang DDL yang dijana hanyalah anggaran daripadanya.

Bagaimana dengan model pelanggan?

Seperti yang dinyatakan di atas, pada klien anda memerlukan salinan skema pangkalan data anda, paparan klien. Tidak perlu dikatakan, pandangan pelanggan ini mesti selari dengan model sebenar. Apakah cara terbaik untuk mencapai matlamat ini? Dengan penjana kod.

Semua pangkalan data menyediakan maklumat meta mereka melalui SQL. Berikut ialah cara untuk mendapatkan semua jadual dalam dialek SQL yang berbeza daripada pangkalan data anda:

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

Pertanyaan ini (atau yang serupa, bergantung pada sama ada anda juga perlu mempertimbangkan pandangan, paparan terwujud, fungsi bernilai jadual) juga dilaksanakan dengan memanggil DatabaseMetaData.getTables() daripada JDBC, atau menggunakan modul meta jOOQ.

Daripada hasil pertanyaan sedemikian, agak mudah untuk menjana sebarang perwakilan sisi klien bagi model pangkalan data anda, tidak kira apa teknologi yang anda gunakan pada klien.

  • Jika anda menggunakan JDBC atau Spring anda boleh mencipta satu set pemalar rentetan
  • Jika anda menggunakan JPA, maka anda boleh menjana entiti itu sendiri
  • Jika anda menggunakan jOOQ anda boleh menjana model meta jOOQ

Bergantung pada berapa banyak keupayaan yang ditawarkan oleh API pelanggan anda (cth jOOQ atau JPA), model meta yang dijana boleh menjadi sangat kaya dan lengkap. Ambil, sebagai contoh, kemungkinan gabungan tersirat, diperkenalkan dalam jOOQ 3.11, yang bergantung pada maklumat meta yang dijana tentang perhubungan utama asing antara jadual anda.

Sekarang sebarang kenaikan pangkalan data akan mengemas kini kod klien secara automatik. Bayangkan sebagai contoh:

ALTER TABLE book RENAME COLUMN title TO book_title;

Adakah anda benar-benar ingin melakukan kerja ini dua kali? Walau apa pun. Kami hanya melaksanakan DDL, menjalankannya melalui saluran paip binaan anda dan dapatkan entiti yang dikemas kini:

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

Atau kelas jOOQ yang dikemas kini. Kebanyakan perubahan DDL juga mempengaruhi semantik, bukan hanya sintaks. Oleh itu, adalah mudah untuk melihat dalam kod yang disusun kod yang akan (atau boleh) terjejas dengan menambah pangkalan data anda.

Satu-satunya kebenaran

Tidak kira teknologi yang anda gunakan, sentiasa ada satu model yang merupakan satu-satunya sumber kebenaran untuk beberapa subsistem - atau sekurang-kurangnya kita harus berusaha untuk ini dan mengelakkan kekeliruan perusahaan di mana "kebenaran" berada di mana-mana dan di mana-mana sekaligus. Segala-galanya boleh menjadi lebih mudah. Jika anda hanya menukar fail XML dengan beberapa sistem lain, hanya gunakan XSD. Lihat model meta INFORMATION_SCHEMA jOOQ dalam bentuk XML:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • XSD difahami dengan baik
  • XSD menandakan kandungan XML dengan sangat baik dan membenarkan pengesahan dalam semua bahasa klien
  • XSD adalah versi yang baik dan sangat serasi ke belakang
  • XSD boleh diterjemahkan ke dalam kod Java menggunakan XJC

Perkara terakhir adalah penting. Apabila berkomunikasi dengan sistem luaran menggunakan mesej XML, kami ingin memastikan bahawa mesej kami adalah sah. Ini sangat mudah dicapai dengan JAXB, XJC dan XSD. Adalah menjadi kegilaan untuk berfikir bahawa, dalam pendekatan reka bentuk yang mengutamakan Java di mana kami menjadikan mesej kami sebagai objek Java, entah bagaimana ia boleh diberikan dengan mudah difahami kepada XML dan dihantar untuk penggunaan ke sistem lain. XML yang dijana dengan cara ini adalah kualiti yang sangat buruk, tidak berdokumen dan sukar untuk dibangunkan. Jika terdapat persetujuan tentang tahap kualiti perkhidmatan (SLA) pada antara muka sedemikian, kami akan segera merosakkannya.

Sejujurnya, inilah yang berlaku sepanjang masa dengan API JSON, tetapi itu cerita lain, saya akan berhujah lain kali ...

Pangkalan data: mereka adalah sama

Bekerja dengan pangkalan data, anda memahami bahawa semuanya pada asasnya adalah sama. Pangkalan data memiliki datanya dan mesti mengurus skema. Sebarang pengubahsuaian yang dibuat pada skema mesti dilaksanakan secara langsung dalam DDL supaya sumber tunggal kebenaran dikemas kini.

Apabila kemas kini sumber telah berlaku, semua pelanggan juga mesti mengemas kini salinan model mereka. Sesetengah pelanggan boleh ditulis dalam Java menggunakan jOOQ dan Hibernate atau JDBC (atau kedua-duanya). Pelanggan lain mungkin ditulis dalam Perl (mari doakan mereka berjaya), yang lain dalam C#. Tidak mengapa. Model utama adalah dalam pangkalan data. Model yang dijana ORM biasanya tidak berkualiti, kurang didokumentasikan dan sukar untuk dibangunkan.

Jadi jangan buat kesilapan. Jangan buat kesilapan dari awal. Bekerja daripada pangkalan data. Bina saluran paip penggunaan yang boleh diautomasikan. Dayakan penjana kod untuk menyalin model pangkalan data anda dengan mudah dan membuangnya pada pelanggan. Dan berhenti bimbang tentang penjana kod. Mereka bagus. Dengan mereka, anda akan menjadi lebih produktif. Apa yang anda perlu lakukan ialah meluangkan sedikit masa untuk menyediakannya dari awal, dan anda akan mempunyai prestasi yang lebih baik selama bertahun-tahun untuk membina cerita projek anda.

Jangan terima kasih lagi, nanti.

Penjelasan

Untuk menjadi jelas: Artikel ini sama sekali tidak menyokong bahawa keseluruhan sistem (iaitu, domain, logik perniagaan, dll., dll.) perlu difleksikan agar sesuai dengan model pangkalan data anda. Apa yang saya bincangkan dalam artikel ini ialah kod klien yang berinteraksi dengan pangkalan data harus bertindak berdasarkan model pangkalan data supaya ia tidak menghasilkan semula model pangkalan data dalam status "kelas pertama". Logik sedemikian biasanya terletak pada lapisan akses data pada klien anda.

Dalam seni bina dua peringkat, yang masih dikekalkan di beberapa tempat, model sistem sedemikian mungkin satu-satunya yang mungkin. Walau bagaimanapun, dalam kebanyakan sistem, lapisan capaian data bagi saya adalah "subsistem" yang merangkumi model pangkalan data.

Pengecualian

Terdapat pengecualian untuk setiap peraturan, dan saya telah mengatakan sebelum ini bahawa pangkalan data pertama dan pendekatan penjanaan kod sumber kadangkala tidak sesuai. Berikut adalah beberapa pengecualian tersebut (mungkin ada yang lain):

  • Apabila skema tidak diketahui dan perlu dibuka. Sebagai contoh, anda menyediakan alat untuk membantu pengguna menavigasi sebarang rajah. Fuh. Tiada penjanaan kod di sini. Tetapi masih - pangkalan data pertama sekali.
  • Apabila litar perlu dijana dengan cepat untuk menyelesaikan beberapa masalah. Contoh ini nampaknya versi corak yang sedikit berenda nilai atribut entiti, iaitu, anda sebenarnya tidak mempunyai skema yang jelas. Dalam kes ini, anda selalunya tidak dapat memastikan sama sekali bahawa RDBMS akan sesuai dengan anda.

Pengecualian secara semula jadi adalah luar biasa. Dalam kebanyakan kes yang melibatkan penggunaan RDBMS, skema diketahui lebih awal, ia berada di dalam RDBMS dan merupakan satu-satunya sumber "kebenaran", dan semua pelanggan perlu memperoleh salinan yang diperoleh daripadanya. Sebaik-baiknya, ini harus melibatkan penjana kod.

Sumber: www.habr.com

Tambah komen