Kebenaran pertama, atau mengapa sistem harus dirancang berdasarkan struktur database

Hei Habr!

Kami terus meneliti topik tersebut Jawa ΠΈ Musim semi, termasuk di tingkat database. Hari ini kami mengundang Anda untuk membaca tentang mengapa, ketika merancang aplikasi besar, struktur databaselah yang harus menjadi penentu, dan bukan kode Java, bagaimana hal ini dilakukan, dan pengecualian apa yang ada pada aturan ini.

Dalam artikel yang agak terlambat ini, saya akan menjelaskan mengapa saya percaya bahwa di hampir semua kasus, model data dalam suatu aplikasi harus dirancang "dari database" daripada "dari kemampuan Java" (atau bahasa klien apa pun yang Anda gunakan. bekerja dengan). Dengan mengambil pendekatan kedua, Anda mempersiapkan diri untuk menghadapi penderitaan dan penderitaan yang panjang begitu proyek Anda mulai berkembang.

Artikel ini ditulis berdasarkan satu pertanyaan, diberikan pada Stack Overflow.

Diskusi menarik tentang reddit dalam beberapa bagian /r/java ΠΈ / r / pemrograman.

Pembuatan kode

Betapa terkejutnya saya ketika ada sebagian kecil pengguna yang, setelah mengenal jOOQ, merasa marah karena jOOQ sangat bergantung pada pembuatan kode sumber untuk beroperasi. Tidak ada yang menghentikan Anda menggunakan jOOQ sesuai keinginan Anda, atau memaksa Anda menggunakan pembuatan kode. Namun cara default (seperti yang dijelaskan dalam manual) dalam bekerja dengan jOOQ adalah Anda memulai dengan skema database (lama), merekayasa baliknya menggunakan generator kode jOOQ untuk mendapatkan sekumpulan kelas yang mewakili tabel Anda, dan kemudian menulis tipe -kueri aman ke tabel ini:

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

Kode dihasilkan secara manual di luar rakitan, atau secara manual di setiap rakitan. Misalnya, regenerasi tersebut mungkin terjadi segera setelahnya Migrasi database jalur terbang, yang juga dapat dilakukan secara manual atau otomatis.

Pembuatan kode sumber

Ada berbagai filosofi, kelebihan dan kekurangan yang terkait dengan pendekatan pembuatan kode ini - manual dan otomatis - yang tidak akan saya bahas secara detail di artikel ini. Namun, secara umum, inti dari kode yang dihasilkan adalah memungkinkan kita untuk mereproduksi di Java β€œkebenaran” yang kita anggap remeh, baik di dalam sistem kita atau di luarnya. Dalam arti tertentu, inilah yang dilakukan kompiler ketika mereka menghasilkan bytecode, kode mesin, atau bentuk kode sumber lainnya - kita mendapatkan representasi "kebenaran" kita dalam bahasa lain, apa pun alasan spesifiknya.

Ada banyak pembuat kode seperti itu. Misalnya, XJC dapat menghasilkan kode Java berdasarkan file XSD atau WSDL. Prinsipnya selalu sama:

  • Ada beberapa kebenaran (internal atau eksternal) - misalnya, spesifikasi, model data, dll.
  • Kami membutuhkan representasi lokal dari kebenaran ini dalam bahasa pemrograman kami.

Selain itu, hampir selalu disarankan untuk membuat representasi seperti itu untuk menghindari redundansi.

Jenis Penyedia dan Pemrosesan Anotasi

Catatan: pendekatan lain yang lebih modern dan spesifik untuk menghasilkan kode untuk jOOQ adalah menggunakan penyedia tipe, seperti yang diterapkan di F#. Dalam hal ini, kode dihasilkan oleh kompiler, sebenarnya pada tahap kompilasi. Pada prinsipnya, kode tersebut tidak ada dalam bentuk aslinya. Java memiliki alat yang serupa, meskipun tidak begitu elegan - pemroses anotasi, misalnya, Lombok.

Dalam arti tertentu, hal yang sama terjadi di sini seperti pada kasus pertama, dengan pengecualian:

  • Anda tidak melihat kode yang dihasilkan (mungkin situasi ini tampaknya tidak terlalu menjijikkan bagi seseorang?)
  • Anda harus memastikan bahwa tipe dapat diberikan, yaitu, "benar" harus selalu tersedia. Hal ini mudah terjadi dalam kasus Lombok, yang menganotasi β€œkebenaran”. Ini sedikit lebih rumit dengan model database yang bergantung pada koneksi langsung yang selalu tersedia.

Apa masalah dengan pembuatan kode?

Selain pertanyaan rumit tentang cara terbaik menjalankan pembuatan kode - secara manual atau otomatis, kami juga harus menyebutkan bahwa ada orang yang percaya bahwa pembuatan kode tidak diperlukan sama sekali. Alasan yang paling sering saya temui adalah sulitnya menyiapkan saluran pipa. Ya, itu sangat sulit. Biaya infrastruktur tambahan timbul. Jika Anda baru memulai dengan produk tertentu (apakah itu jOOQ, atau JAXB, atau Hibernate, dll.), menyiapkan lingkungan produksi membutuhkan waktu yang lebih baik Anda habiskan untuk mempelajari API itu sendiri sehingga Anda dapat mengambil manfaat darinya. .

Jika biaya yang terkait dengan pemahaman struktur generator terlalu tinggi, maka memang API melakukan pekerjaan yang buruk dalam kegunaan generator kode (dan kemudian ternyata penyesuaian pengguna di dalamnya juga rumit). Kegunaan harus menjadi prioritas tertinggi untuk API semacam itu. Tapi ini hanyalah salah satu argumen yang menentang pembuatan kode. Jika tidak, menulis representasi lokal tentang kebenaran internal atau eksternal adalah hal yang sepenuhnya manual.

Banyak yang akan mengatakan bahwa mereka tidak punya waktu untuk melakukan semua ini. Mereka kehabisan tenggat waktu untuk Produk Super mereka. Suatu hari nanti kami akan membereskan konveyor perakitan, kami akan punya waktu. Saya akan menjawabnya:

Kebenaran pertama, atau mengapa sistem harus dirancang berdasarkan struktur database
Asli, Alan O'Rourke, Tumpukan Audiens

Namun di Hibernate/JPA sangat mudah untuk menulis kode Java.

Benar-benar. Bagi Hibernate dan penggunanya, ini merupakan berkah sekaligus kutukan. Di Hibernate Anda cukup menulis beberapa entitas, seperti ini:

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

Dan hampir semuanya sudah siap. Sekarang terserah pada Hibernate untuk menghasilkan "detail" kompleks tentang bagaimana tepatnya entitas ini akan didefinisikan 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 mulai menjalankan aplikasi. Kesempatan yang sangat keren untuk memulai dengan cepat dan mencoba berbagai hal.

Namun, izinkan saya. Saya berbohong.

  • Akankah Hibernate benar-benar menerapkan definisi kunci utama bernama ini?
  • Akankah Hibernate membuat indeks di TITLE? – Saya tahu pasti bahwa kita akan membutuhkannya.
  • Akankah Hibernate membuat identifikasi kunci ini dalam Spesifikasi Identitas?

Mungkin tidak. Jika Anda mengembangkan proyek dari awal, akan lebih mudah jika Anda membuang database lama dan membuat database baru segera setelah Anda menambahkan anotasi yang diperlukan. Dengan demikian, entitas Buku pada akhirnya akan berbentuk:

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

Dingin. Diperbarui. Sekali lagi, dalam hal ini akan sangat mudah pada awalnya.

Tapi Anda harus membayarnya nanti

Cepat atau lambat Anda harus masuk ke produksi. Saat itulah model ini akan berhenti bekerja. Karena:

Dalam produksi, jika perlu, tidak mungkin lagi membuang database lama dan memulai dari awal. Basis data Anda akan menjadi warisan.

Mulai sekarang dan selamanya Anda harus menulis Skrip migrasi DDL misalnya menggunakan Flyway. Apa yang akan terjadi pada entitas Anda dalam kasus ini? Anda dapat mengadaptasinya secara manual (dan dengan demikian menggandakan beban kerja Anda), atau Anda dapat meminta Hibernate untuk membuat ulang mereka untuk Anda (seberapa besar kemungkinan yang dihasilkan dengan cara ini memenuhi harapan Anda?) Apa pun yang terjadi, Anda kalah.

Jadi, begitu Anda mulai berproduksi, Anda memerlukan hot patch. Dan mereka harus segera diproduksi. Karena Anda tidak mempersiapkan dan tidak mengatur kelancaran migrasi ke produksi, Anda dengan liar menambal semuanya. Dan kemudian Anda tidak lagi punya waktu untuk melakukan semuanya dengan benar. Dan Anda mengkritik Hibernate, karena itu selalu merupakan kesalahan orang lain, hanya saja bukan Anda...

Sebaliknya, segala sesuatunya bisa saja dilakukan dengan cara yang berbeda sejak awal. Misalnya saja memasang roda bundar pada sepeda.

Basis data dulu

"Kebenaran" sebenarnya dalam skema database Anda dan "kedaulatan" atasnya terletak di dalam database. Skema ini hanya ditentukan dalam database itu sendiri dan tidak di tempat lain, dan setiap klien memiliki salinan skema ini, jadi masuk akal untuk menegakkan kepatuhan terhadap skema dan integritasnya, untuk melakukannya dengan benar di database - tempat informasinya berada. disimpan.
Ini adalah kebijaksanaan kuno, bahkan sudah usang. Kunci primer dan unik bagus. Kunci asing bagus. Memeriksa batasan itu bagus. Pernyataan - Bagus.

Apalagi bukan itu saja. Misalnya, saat menggunakan Oracle, Anda mungkin ingin menentukan:

  • Di tablespace mana meja Anda berada?
  • Berapa nilai PCTFREE-nya?
  • Berapa ukuran cache di urutan Anda (di belakang id)

Hal ini mungkin tidak penting dalam sistem kecil, namun Anda tidak perlu menunggu hingga beralih ke ranah data besarβ€”Anda dapat mulai memanfaatkan optimalisasi penyimpanan yang disediakan vendor seperti yang disebutkan di atas lebih cepat. Tak satu pun ORM yang saya lihat (termasuk jOOQ) menyediakan akses ke rangkaian lengkap opsi DDL yang mungkin ingin Anda gunakan dalam database Anda. ORM menawarkan beberapa alat yang membantu Anda menulis DDL.

Namun pada akhirnya, sirkuit yang dirancang dengan baik ditulis tangan di DDL. DDL apa pun yang dihasilkan hanyalah perkiraan saja.

Bagaimana dengan model klien?

Seperti disebutkan di atas, pada klien Anda memerlukan salinan skema database Anda, tampilan klien. Perlu disebutkan lagi, tampilan klien ini harus sinkron dengan model sebenarnya. Apa cara terbaik untuk mencapai hal ini? Menggunakan generator kode.

Semua database menyediakan informasi metanya melalui SQL. Berikut cara mendapatkan semua tabel dari database Anda dalam dialek SQL yang berbeda:

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

Kueri ini (atau yang serupa, bergantung pada apakah Anda juga harus mempertimbangkan tampilan, tampilan terwujud, fungsi bernilai tabel) juga dijalankan dengan memanggil DatabaseMetaData.getTables() dari JDBC, atau menggunakan meta-modul jOOQ.

Dari hasil kueri tersebut, relatif mudah untuk menghasilkan representasi sisi klien dari model database Anda, terlepas dari teknologi apa yang Anda gunakan pada klien.

  • Jika Anda menggunakan JDBC atau Spring, Anda dapat membuat sekumpulan konstanta string
  • Jika Anda menggunakan JPA, Anda dapat membuat entitasnya sendiri
  • Jika Anda menggunakan jOOQ, Anda dapat membuat model meta jOOQ

Bergantung pada seberapa banyak fungsionalitas yang ditawarkan oleh API klien Anda (misalnya jOOQ atau JPA), model meta yang dihasilkan bisa sangat kaya dan lengkap. Ambil contoh, kemungkinan gabungan implisit, diperkenalkan di jOOQ 3.11, yang bergantung pada informasi meta yang dihasilkan tentang hubungan kunci asing yang ada di antara tabel Anda.

Sekarang setiap penambahan basis data akan secara otomatis memperbarui kode klien. Bayangkan misalnya:

ALTER TABLE book RENAME COLUMN title TO book_title;

Apakah Anda benar-benar ingin melakukan pekerjaan ini dua kali? Sama sekali tidak. Cukup komit DDL, jalankan melalui pipeline build Anda, dan dapatkan entitas yang diperbarui:

@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 diperbarui. Kebanyakan perubahan DDL juga mempengaruhi semantik, bukan hanya sintaksis. Oleh karena itu, akan berguna untuk melihat kode yang dikompilasi untuk melihat kode apa yang akan (atau dapat) terpengaruh oleh penambahan database Anda.

Satu-satunya kebenaran

Terlepas dari teknologi apa yang Anda gunakan, selalu ada satu model yang merupakan satu-satunya sumber kebenaran untuk beberapa subsistem - atau, paling tidak, kita harus mengupayakannya dan menghindari kebingungan perusahaan, di mana β€œkebenaran” ada di mana-mana dan tidak di mana pun sekaligus. . Segalanya bisa menjadi lebih sederhana. Jika Anda hanya bertukar file XML dengan sistem lain, cukup gunakan XSD. Lihatlah meta-model INFORMATION_SCHEMA dari jOOQ dalam bentuk XML:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • XSD dipahami dengan baik
  • XSD memberi token pada konten XML dengan sangat baik dan memungkinkan validasi dalam semua bahasa klien
  • XSD memiliki versi yang baik dan memiliki kompatibilitas mundur yang canggih
  • XSD dapat diterjemahkan ke dalam kode Java menggunakan XJC

Poin terakhir ini penting. Saat berkomunikasi dengan sistem eksternal menggunakan pesan XML, kami ingin memastikan bahwa pesan kami valid. Ini sangat mudah dicapai dengan menggunakan JAXB, XJC dan XSD. Benar-benar gila untuk berpikir bahwa, dengan pendekatan desain "Java first" di mana kita menjadikan pesan kita sebagai objek Java, pesan tersebut dapat dipetakan secara koheren ke XML dan dikirim ke sistem lain untuk dikonsumsi. XML yang dihasilkan dengan cara ini akan memiliki kualitas yang sangat buruk, tidak terdokumentasi, dan sulit untuk dikembangkan. Jika ada perjanjian tingkat layanan (SLA) untuk antarmuka seperti itu, kami akan segera mengacaukannya.

Sejujurnya, ini adalah apa yang terjadi sepanjang waktu dengan API JSON, tapi itu cerita lain, saya akan bertengkar lain kali...

Basis data: keduanya sama

Saat bekerja dengan database, Anda menyadari bahwa semuanya pada dasarnya serupa. Pangkalan tersebut memiliki datanya dan harus mengelola skema tersebut. Setiap modifikasi yang dilakukan pada skema harus diimplementasikan langsung di DDL sehingga satu-satunya sumber kebenaran diperbarui.

Ketika pembaruan sumber terjadi, semua klien juga harus memperbarui salinan model mereka. Beberapa klien dapat ditulis dalam Java menggunakan jOOQ dan Hibernate atau JDBC (atau keduanya). Klien lain dapat ditulis dalam Perl (kami hanya mendoakan semoga mereka beruntung), sementara yang lain dapat ditulis dalam C#. Tidak masalah. Model utamanya ada di database. Model yang dihasilkan menggunakan ORM biasanya berkualitas buruk, tidak terdokumentasi dengan baik, dan sulit dikembangkan.

Jadi jangan membuat kesalahan. Jangan membuat kesalahan sejak awal. Bekerja dari database. Bangun jalur penerapan yang dapat diotomatisasi. Aktifkan pembuat kode untuk memudahkan penyalinan model database Anda dan membuangnya ke klien. Dan berhentilah mengkhawatirkan pembuat kode. Mereka bagus. Bersama mereka Anda akan menjadi lebih produktif. Anda hanya perlu meluangkan sedikit waktu untuk menyiapkannya dari awal - dan kemudian peningkatan produktivitas selama bertahun-tahun menanti Anda, yang akan menjadi sejarah proyek Anda.

Jangan berterima kasih padaku dulu, nanti.

Klarifikasi

Untuk lebih jelasnya: Artikel ini sama sekali tidak menganjurkan bahwa Anda perlu membengkokkan seluruh sistem (yaitu, domain, logika bisnis, dll., dll.) agar sesuai dengan model database Anda. Apa yang saya katakan dalam artikel ini adalah bahwa kode klien yang berinteraksi dengan database harus bertindak berdasarkan model database, sehingga kode itu sendiri tidak mereproduksi model database dalam status "kelas satu". Logika ini biasanya terletak di lapisan akses data pada klien Anda.

Dalam arsitektur dua tingkat, yang masih dipertahankan di beberapa tempat, model sistem seperti itu mungkin satu-satunya yang mungkin. Namun, di sebagian besar sistem, lapisan akses data menurut saya adalah "subsistem" yang merangkum model database.

Pengecualian

Ada pengecualian untuk setiap aturan, dan saya telah mengatakan bahwa pendekatan yang mengutamakan basis data dan pembuatan kode sumber terkadang tidak tepat. Berikut adalah beberapa pengecualian (mungkin ada pengecualian lainnya):

  • Ketika skema tidak diketahui dan perlu ditemukan. Misalnya, Anda adalah penyedia alat yang membantu pengguna menavigasi diagram apa pun. Ugh. Tidak ada pembuatan kode di sini. Tapi tetap saja, database adalah yang utama.
  • Ketika sebuah sirkuit harus dibuat dengan cepat untuk menyelesaikan beberapa masalah. Contoh ini tampak seperti versi pola yang sedikit aneh nilai atribut entitas, yaitu, Anda tidak memiliki skema yang jelas. Dalam hal ini, Anda sering kali bahkan tidak yakin bahwa RDBMS cocok untuk Anda.

Pengecualian pada dasarnya adalah pengecualian. Dalam kebanyakan kasus yang melibatkan penggunaan RDBMS, skemanya sudah diketahui sebelumnya, skema tersebut berada di dalam RDBMS dan merupakan satu-satunya sumber β€œkebenaran”, dan semua klien harus memperoleh salinan yang berasal dari skema tersebut. Idealnya, Anda perlu menggunakan pembuat kode.

Sumber: www.habr.com

Tambah komentar