Kebenaran pertama-tama, atau mengapa sistem perlu dirancang berdasarkan pada perangkat basis data

Halo, Habr!

Kami terus mengeksplorasi topik Jawa dan Musim Semi , termasuk di tingkat basis data. Hari ini kami menyarankan membaca tentang mengapa, ketika merancang aplikasi besar, itu adalah struktur database, dan bukan kode Java, yang harus memiliki makna yang menentukan tentang bagaimana hal ini dilakukan, dan apa pengecualian dari aturan ini.

Dalam artikel yang agak terlambat ini, saya akan menjelaskan mengapa saya percaya bahwa dalam hampir semua kasus model data dalam aplikasi harus dirancang "berdasarkan pada basis data" dan bukan "berdasarkan pada kemampuan Java" (atau bahasa klien lain yang bekerja dengan Anda). Memilih pendekatan kedua, Anda memulai perjalanan panjang rasa sakit dan penderitaan segera setelah proyek Anda mulai tumbuh.

Artikel ini didasarkan pada satu pertanyaan yang diajukan pada Stack Overflow.

Diskusi menarik tentang reddit di bagian / r / java dan / r / pemrograman .

Pembuatan kode


Betapa terkejutnya saya bahwa ada lapisan kecil pengguna yang, setelah berkenalan dengan JOOQ, sangat marah dengan kenyataan bahwa ketika bekerja JOOQ sangat bergantung pada pembuatan kode sumber. Tidak ada yang mengganggu Anda untuk menggunakan jOOQ sesuai keinginan Anda, dan tidak memaksa Anda untuk menggunakan pembuatan kode. Tetapi secara default (seperti yang dijelaskan dalam manual), bekerja dengan jOOQ terjadi seperti ini: Anda mulai dengan skema database (yang diwarisi), merekayasa balik dengan generator kode jOOQ, sehingga Anda mendapatkan satu set kelas yang mewakili tabel Anda, dan kemudian tulis kueri tipe-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 baik secara manual di luar rakitan, atau secara manual dengan setiap rakitan. Sebagai contoh, regenerasi tersebut dapat terjadi segera setelah migrasi database Flyway, yang juga dapat dilakukan secara manual atau otomatis .

Pembuatan kode sumber


Berbagai filosofi, kelebihan dan kekurangan dikaitkan dengan pendekatan seperti untuk pembuatan kode - manual dan otomatis - yang saya tidak akan membahas secara rinci dalam artikel ini. Tetapi, secara umum, inti dari kode yang dihasilkan adalah memungkinkannya untuk mereproduksi di Jawa bahwa "kebenaran" yang kita terima begitu saja, baik di dalam sistem kami atau di luarnya. Dalam arti tertentu, kompiler yang menghasilkan bytecode, kode mesin, atau semacam kode berbasis sumber lainnya melakukan hal yang sama - kami mendapatkan representasi dari "kebenaran" kami dalam bahasa lain, terlepas dari alasan spesifik.

Ada banyak generator 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.
  • Kita membutuhkan representasi lokal dari kebenaran ini dalam bahasa pemrograman kita.

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

Jenis Penyedia dan Pemrosesan Anotasi


Catatan: pendekatan lain, lebih modern dan spesifik untuk pembuatan kode untuk jOOQ dikaitkan dengan penggunaan penyedia jenis, dalam bentuk di mana mereka diimplementasikan dalam F # . Dalam hal ini, kode dihasilkan oleh kompiler, sebenarnya pada tahap kompilasi. Dalam bentuk sumber, kode semacam itu, pada prinsipnya, tidak ada. Ada yang serupa, meskipun alat yang kurang elegan di Jawa - ini adalah prosesor anotasi seperti 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 bagi seseorang tampaknya tidak terlalu menjijikkan?)
  • , , , ยซยป . Lombok, โ€œโ€. , .

?


Selain pertanyaan rumit tentang bagaimana lebih baik memulai pembuatan kode - secara manual atau otomatis, perlu disebutkan bahwa ada orang yang percaya bahwa pembuatan kode tidak diperlukan sama sekali. Alasan untuk sudut pandang ini, yang paling sering saya temui, adalah sulitnya mengkonfigurasi pipa rakitan. Ya, sangat sulit. Ada biaya infrastruktur tambahan. Jika Anda baru mulai bekerja dengan produk tertentu (baik itu jOOQ, atau JAXB, atau Hibernate, dll.), Diperlukan waktu untuk mengatur lingkungan kerja yang ingin Anda habiskan untuk mempelajari API itu sendiri, dan kemudian mengekstrak nilai darinya.

Jika biaya yang terkait dengan pemahaman perangkat generator terlalu besar, maka, memang, API sedikit bekerja pada kegunaan dari generator kode (dan di masa depan ternyata konfigurasi pengguna di dalamnya rumit). Kemudahan penggunaan harus menjadi prioritas tertinggi untuk API semacam itu. Tapi ini hanya satu argumen terhadap pembuatan kode. Untuk selebihnya, ini sepenuhnya sepenuhnya manual untuk menulis representasi lokal dari kebenaran internal atau eksternal.

Banyak yang akan mengatakan bahwa mereka tidak punya waktu untuk melakukan semua ini. Mereka memiliki tenggat waktu untuk Produk Super mereka. Beberapa waktu kemudian kita menyisir konveyor perakitan, itu akan tepat waktu. Saya akan menjawab mereka:


Asli , Alan O'Rourke, Penonton Stack

Tapi di Hibernate / JPA sangat mudah untuk menulis kode "untuk Java".

Betulkah. Untuk Hibernate dan penggunanya, ini adalah 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 nasib Hibernate adalah untuk menghasilkan "detail" kompleks tentang bagaimana entitas ini akan didefinisikan pada DDL dari "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 kami mulai menjalankan aplikasi. Kesempatan yang sangat keren untuk memulai dengan cepat dan mencoba berbagai hal.

Namun, izinkan. Saya menipu.

  • Apakah Hibernate benar-benar menerapkan definisi kunci primer bernama ini?
  • Akankah Hibernate membuat indeks di TITLE? "Aku tahu pasti bahwa kita akan membutuhkannya."
  • Apakah Hibernate benar-benar membuat kunci ini dapat diidentifikasi dalam Spesifikasi Identitas?

Mungkin tidak. Jika Anda mengembangkan proyek Anda dari awal, selalu mudah untuk hanya membuang database lama dan menghasilkan yang baru segera setelah Anda menambahkan anotasi yang diperlukan. Jadi, entitas Buku pada 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;
}

Keren. Diperbaiki. Sekali lagi, dalam hal ini, pada awalnya akan sangat mudah.

Tetapi kemudian Anda harus membayarnya


Cepat atau lambat Anda harus mulai berproduksi. Saat itu, model seperti itu akan berhenti bekerja. Karena:

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

Mulai sekarang, Anda harus menulis skrip migrasi DDL, misalnya, menggunakan Flyway . Lalu apa yang terjadi pada entitas Anda? Anda dapat mengadaptasinya secara manual (dan karenanya melipatgandakan beban kerja Anda), atau Anda dapat memesan Hibernate untuk membuatnya kembali untuk Anda (seberapa besar kemungkinan yang dihasilkan dengan cara ini akan memenuhi harapan Anda?) Anda tetap kalah.

Jadi, segera setelah Anda mulai berproduksi, Anda akan memerlukan hot patch. Dan mereka harus dimasukkan ke dalam produksi dengan sangat cepat. Karena Anda belum menyiapkan dan mengatur konveyor migrasi Anda untuk produksi yang lancar, Anda menambal semuanya dengan liar. Dan kemudian Anda tidak punya waktu untuk melakukan semuanya dengan benar. Dan memarahi Hibernate, karena siapa pun selalu harus disalahkan, tetapi bukan Anda ...

Sebaliknya, sejak awal semuanya bisa dilakukan dengan cara yang sama sekali berbeda. Misalnya, letakkan roda bundar di atas sepeda.

Database dulu


"Kebenaran" yang sebenarnya dalam skema basis data Anda dan "kedaulatan" di atasnya terletak di dalam basis data. Skema ini didefinisikan hanya dalam database itu sendiri dan di tempat lain, dan masing-masing klien memiliki salinan skema ini, sehingga sangat disarankan untuk memaksakan kepatuhan dengan skema dan integritasnya, lakukan secara langsung dalam database - di mana informasi disimpan.
Ini bahkan merupakan kearifan usang. Kunci primer dan unik bagus. Kunci asing baik. Memeriksa pembatasan itu bagus. Pernyataannya bagus.

Apalagi ini belum semuanya. Misalnya, menggunakan Oracle, Anda mungkin ingin menentukan:

  • Tablespace di mana tabel Anda?
  • Apa nilai PCTFREE-nya?
  • Berapa ukuran cache dalam urutan Anda (di belakang pengidentifikasi)

Mungkin semua ini tidak penting dalam sistem kecil, tetapi tidak perlu menunggu transisi ke area "data besar" - adalah mungkin dan jauh lebih awal untuk mulai mendapat manfaat dari optimasi penyimpanan yang disediakan oleh pemasok, seperti yang disebutkan di atas. Tidak satu pun dari ORM yang saya lihat (termasuk jOOQ) memberikan akses ke set lengkap opsi DDL yang mungkin ingin Anda gunakan dalam database Anda. ORM menawarkan beberapa alat yang membantu menulis DDL.

Tetapi pada akhirnya, sirkuit yang dirancang dengan baik ditulis secara manual dalam DDL. Setiap DDL yang dihasilkan hanya perkiraan saja.

Bagaimana dengan model klien?


Seperti disebutkan di atas, pada klien Anda akan memerlukan salinan skema database Anda, tampilan klien. Tidak perlu dikatakan, tampilan klien ini harus disinkronkan dengan model nyata. Apa cara terbaik untuk mencapai ini? Menggunakan generator kode.

Semua database menyediakan informasi meta mereka melalui SQL. Inilah cara mendapatkan semua tabel dalam dialek SQL yang berbeda dari database 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

Kueri ini (atau yang serupa, tergantung pada apakah Anda juga harus mempertimbangkan representasi, representasi terwujud, fungsi dengan nilai tabel) juga dilakukan menggunakan panggilan DatabaseMetaData.getTables()dari JDBC, atau menggunakan meta-modul jOOQ.

Dari hasil pertanyaan seperti itu, relatif mudah untuk menghasilkan pandangan klien dari model database Anda, terlepas dari teknologi apa yang digunakan pada klien Anda.

  • Jika Anda menggunakan JDBC atau Spring, Anda bisa membuat seperangkat konstanta string
  • Jika menggunakan JPA, Anda dapat membuat entitas sendiri
  • Jika menggunakan jOOQ, Anda dapat membuat meta-model jOOQ

Bergantung pada berapa banyak fitur yang ditawarkan oleh API klien Anda (mis. JOOQ atau JPA), meta-model yang dihasilkan dapat benar-benar kaya dan lengkap. Ambil, misalnya , kemungkinan gabungan implisit yang muncul di jOOQ 3.11 , yang bergantung pada meta-informasi yang dihasilkan tentang hubungan kunci asing antara tabel Anda.

Sekarang setiap penambahan basis data akan secara otomatis menyebabkan pembaruan 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. Perbaiki DDL, jalankan melalui pipa perakitan 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. Sebagian besar perubahan DDL juga memengaruhi semantik, bukan hanya sintaksis. Oleh karena itu, akan lebih mudah untuk melihat dalam kode yang dikompilasi kode mana yang akan (atau mungkin) dipengaruhi oleh kenaikan basis data Anda.

Satu-satunya kebenaran


Tidak peduli teknologi apa yang Anda gunakan, selalu ada satu model yang merupakan satu-satunya sumber kebenaran untuk beberapa subsistem - atau, setidaknya, kita harus berjuang untuk ini dan menghindari kebingungan perusahaan seperti itu, di mana "kebenaran" ada di mana-mana dan di mana pun. Semuanya bisa lebih sederhana. Jika Anda hanya bertukar file XML dengan beberapa 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 XML
  • XSD
  • XSD Java XJC

Poin terakhir itu penting. Saat berkomunikasi dengan sistem eksternal menggunakan pesan XML, kami ingin memastikan validitas pesan kami. Ini sangat mudah dicapai dengan JAXB, XJC dan XSD. Akan sangat gila untuk berharap bahwa, ketika mendekati desain "Java first", di mana kita membuat pesan kita dalam bentuk objek Java, mereka entah bagaimana dapat dengan jelas ditampilkan dalam XML dan dikirim untuk dikonsumsi ke sistem lain. XML yang dihasilkan dengan cara ini akan memiliki kualitas yang sangat buruk, tidak didokumentasikan, dan akan sulit untuk dikembangkan. Jika ada kesepakatan pada tingkat kualitas layanan (SLA) pada antarmuka seperti itu, kami akan segera merusaknya.

Jujur, itulah yang terjadi sepanjang waktu dari API ke JSON, tapi itu cerita lain, saya akan bersumpah lain kali ...

Database: itu hal yang sama


Ketika bekerja dengan basis data, Anda memahami bahwa mereka pada dasarnya serupa. Pangkalan memiliki datanya dan harus mengelola skema. Setiap modifikasi yang dilakukan pada sirkuit harus diimplementasikan langsung pada DDL untuk memperbarui satu sumber kebenaran.

Ketika pembaruan sumber telah terjadi, semua klien juga harus memperbarui salinan model mereka. Beberapa klien dapat ditulis dalam Java menggunakan jOOQ dan Hibernate atau JDBC (atau sekaligus). Klien lain dapat ditulis dalam Perl (tetap berharap mereka beruntung), dan yang lain dalam C #. Tidak masalah. Model utama ada di database. Model yang dihasilkan menggunakan ORM, biasanya berkualitas rendah, tidak terdokumentasi dengan baik dan sulit untuk dikembangkan.

Karena itu, jangan membuat kesalahan. Jangan membuat kesalahan sejak awal. Bekerja dari basis data. Bangun pipa penyebaran yang dapat diotomatisasi. Nyalakan generator kode untuk membuatnya nyaman untuk menyalin model database Anda dan membuangnya ke klien. Dan berhenti khawatir tentang pembuat kode. Mereka baik. Dengan mereka Anda akan menjadi lebih produktif. Anda hanya perlu meluangkan sedikit waktu sejak awal untuk mengkonfigurasinya - dan kemudian Anda akan memiliki peningkatan produktivitas bertahun-tahun yang akan membentuk sejarah proyek Anda.

Sampai saat itu, terima kasih.

Penjelasan


Untuk kejelasan: Artikel ini sama sekali tidak menganjurkan bahwa di bawah model database Anda, Anda perlu membengkokkan seluruh sistem (mis., Area subjek, logika bisnis, dll., Dll.). Dalam artikel ini, saya mengatakan bahwa kode klien yang berinteraksi dengan database harus bertindak berdasarkan model database sehingga tidak mereproduksi model database dalam status "kelas". Logika ini biasanya terletak di tingkat 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, tingkat akses data menurut saya adalah "subsistem" yang merangkum model basis data.

Pengecualian


Ada pengecualian untuk aturan apa pun, dan saya telah mengatakan bahwa pendekatan dengan keutamaan basis data dan pembuatan kode sumber terkadang tidak tepat. Berikut adalah beberapa pengecualian (mungkin ada yang lain):

  • Ketika sirkuit tidak diketahui, dan harus dibuka. Misalnya, Anda adalah penyedia alat untuk membantu pengguna menavigasi skema apa pun. Fiuh Tidak ada pembuatan kode. Tapi tetap saja - database di atas segalanya.
  • Ketika sirkuit harus dihasilkan dengan cepat untuk memecahkan masalah tertentu. Contoh ini tampak seperti versi yang sedikit fantastis dari pola nilai atribut entitas , yaitu, Anda benar-benar tidak memiliki skema yang terdefinisi dengan baik. Dalam hal ini, seringkali mustahil untuk memastikan bahwa RDBMS tepat untuk Anda.

Pengecualian pada dasarnya luar biasa. Dalam kebanyakan kasus yang melibatkan penggunaan RDBMS, skema ini diketahui sebelumnya, terletak di dalam RDBMS dan merupakan satu-satunya sumber "kebenaran", dan semua pelanggan harus mendapatkan salinan yang berasal darinya. Idealnya, Anda perlu menggunakan generator kode.

All Articles