PHP: mengubah struktur basis data dalam pengembangan tim



Di dunia PHP, alat migrasi struktur basis data sudah terkenal - Doktrin , Phinx dari CakePHP, dari Laravel , dari Yii - ini adalah hal pertama yang terlintas dalam pikiran. Tentunya ada selusin lagi. Dan kebanyakan dari mereka bekerja dengan migrasi - perintah untuk membuat perubahan tambahan ke skema database.

Saya tidak akan menjelaskan mengapa ini terjadi, ada banyak posting tentang topik ini di Habré. Sebagai contoh:


Selanjutnya, pengembangan pengalaman saya sebagai tim dengan perubahan konstan dalam struktur database di cabang yang berbeda.

SQL mentah vs PHP api


Kami menulis migrasi dalam SQL murni. Banyak alat menyediakan api PHP untuk menulis instruksi yang diterjemahkan ke dalam kode SQL. Sekarang saya tidak mengerti mengapa ini? Alat seperti itu akan selalu terbatas dalam kemampuannya. Mereka tidak mengizinkan untuk menulis instruksi spesifik untuk mesin tertentu, Anda masih harus menggunakan SQL murni. Saya tidak berbicara tentang prosedur dan pandangan penulisan.

Seseorang mengeluh bahwa dia tidak ingin mempelajari sintaksis perintah ALTER ... Ya, saya tidak tahu, saya membuka direktori dan menulis contoh gunung, terutama dalam proyek besar.

Migrasi data (INSERT, UPDATE) juga selalu ditulis dalam SQL. Karena Anda tidak pernah bisa mengandalkan versi ORM dan Model saat ini. Dalam satu revisi mereka, yang lain tidak lagi.

Sebagai contoh:

Rollback Country::delete()->where(....)->execute(); 

Ingin memutar kembali keadaan basis data. Dan kelas PHP ini tidak lagi dalam repo. Anda perlu mencari komit terakhir di mana dia berada dan mundur dari sana. Brrr ...

Oleh karena itu, SQL sederhana dan dapat diandalkan:

 --TRANSACTION --UP ALTER TABLE authors ADD COLUMN code INT; ALTER TABLE posts ADD COLUMN slug TEXT; UPDATE authors SET ... --DOWN ALTER TABLE authors DROP COLUMN code; ALTER TABLE posts DROP COLUMN slug; 

Transaksi dalam DDL


Dengan transisi ke PostgreSQL, saya lupa tentang migrasi yang rusak seperti mimpi buruk - migrasi jatuh di tengah, ada yang terguling, ada yang hilang, duduk dan tangan kanan ... Ini memaksa kami untuk menulis perintah baris tunggal atom dan menjalankannya satu per satu. Semuanya sederhana dengan transaksi: jika ada yang rusak - semuanya berputar kembali (well, hampir semuanya))). Perbaiki dan mulai kembali. Perakitan otomatis bekerja dengan keras, jika sesuatu jatuh, ia dengan cepat memperbaiki dan naik.

Tampilan (views) dan fungsi


Masalahnya di sini adalah bahwa mereka tidak dapat diperbarui secara bertahap, seperti ALTER dalam tabel. Butuh DROP dan BUAT. Yaitu pada diferensial (teks migrasi) sama sekali tidak jelas apa yang berubah pada akhirnya. Terutama ketika logika dipelintir, itu agak tidak nyaman. Sebagai contoh:

 --UP DROP VIEW ... CREATE VIEW mvstock AS SELECT (now() - '7 days'::interval) AS refreshed_at, o.pid, COALESCE(sum(o.debit), 0)::integer AS debit, COALESCE(sum(o.credit) FILTER (WHERE d.type <> 104), 0)::integer AS credit, COALESCE(sum(o.debit), 0) - COALESCE(sum(o.credit), 0)::integer AS total FROM operations o JOIN docs d ON d.id = o.doc_id AND d.deleted_at IS NULL WHERE d.closed_at < (now() - '7 days'::interval) AND d.type <> 500 GROUP BY o.pid WITH DATA; --DOWN DROP VIEW ... CREATE VIEW mvstock AS SELECT (now() - '10 days'::interval) AS refreshed_at, o.pid, COALESCE(sum(o.debit), 0)::integer AS debit, COALESCE(sum(o.credit) FILTER (WHERE d.type <> 104), 0)::integer AS credit, COALESCE(sum(o.debit), 0) - COALESCE(sum(o.credit), 0)::integer AS total FROM operations o JOIN docs d ON d.id = o.doc_id AND d.deleted_at IS NULL WHERE d.closed_at < (now() - '10 days'::interval) AND d.type <> 500 GROUP BY o.pid WITH DATA; 

Apa yang berubah di sini?

Kami berhenti pada fakta bahwa di sebelah migrasi adalah ayah, tempat tampilan saat ini dan kode prosedur disimpan, yang diperbarui dan disalin dalam migrasi rollback.

Dan sekarang diffnya menjadi seperti:



Kembali di Avito, kami membuat solusi menarik untuk memvariasikan kode prosedur yang tersimpan.

Secara umum, kasus ini menimbulkan masalah yang bagus - bagaimana melihat sejarah perubahan pada objek tertentu dari struktur database. Untuk setiap tabel, saya ingin melihat riwayat perubahan sehubungan dengan solusi tugas tertentu.



Ditemukan di Habré pendekatan yang menarik untuk otomatisasi memperbaiki perubahan dalam struktur basis data.

Bekerja dengan cabang


Rasa sakit abadi saya adalah bagaimana beralih antara dua cabang A- dan B, yang masing-masing telah diedit pada struktur database.



Diperlukan untuk mengembalikan migrasi di cabang-A (kita juga harus ingat yang mana dan berapa banyak), kemudian beralih ke cabang-B dan memutar migrasi baru. Oke, jika pengeditan kami kompatibel dan saya bisa beralih ke cabang kedua dan memutar migrasi tambahan dari B.

Dan jika tidak? Dan jika saya memiliki lebih dari satu cabang seperti itu? Dan kemudian gulung balik semua kondisi ulasan ini? Saya selalu membencinya ...

Sekarang, ketika beralih ke cabang orang lain, saya dapat secara otomatis menghapus migrasi orang lain dan menggulir yang sekarang:



dimana:

D - migrasi-A yang diluncurkan di cabang-A, tetapi tidak ada di cabang saat ini, dan disarankan untuk menghapusnya
A - migrasi-B yang muncul di cabang baru dan perlu digulirkan

Menjadi sangat nyaman saat pengujian dan perakitan otomatis di satu pangkalan. Ketika tidak ada akal atau peluang untuk setiap cabang untuk membuat basis dari awal. Beralih ke cabang dan secara otomatis menyinkronkan keadaan database.

Penomoran dan urutan eksekusi


Semua alat yang saya tahu migrasi cap waktunya adalah solusi yang baik. Jika saya menulis beberapa migrasi, urutan yang diperlukan dipertahankan. Pengembang lain dapat memiliki tanggal di utas lain, bahkan utas saya - tetapi tidak masalah dalam urutan apa kami bergabung dengannya, perubahan kami tidak tergantung satu sama lain. Bahkan jika kita bekerja dengan tabel yang sama (tambahkan dengan kolom), maka semua perubahan yang diperlukan akan terjadi dalam urutan apa pun. Hal utama adalah bahwa urutan suntingan dependen saya dihormati.



Saya tidak mempertimbangkan kasus ketika kita perlu mengedit hal yang sama - poin ini selalu konsisten. Nah, atau akan ada kegagalan pada tahap perakitan dan pengujian.

Ini adalah contoh yang menarik.

Kami melakukan pengeditan yang berbeda dalam satu tampilan atau prosedur, mis. dalam struktur yang diperbarui melalui penghapusan. Yaitu Misalnya, saya menambahkan kolom col_A ke tampilan, dan rekan saya col_B. Dengan demikian, jika kodenya diluncurkan setelah saya, maka kolomnya tidak akan memiliki kolom saya:

 CREATE VIEW vusers AS SELECT login, name, -- .... 
cabang-Acabang-B
 DROP VIEW vusers; CREATE VIEW vusers AS SELECT login, name, col_A, -- .... 
 DROP VIEW vusers; CREATE VIEW vusers AS SELECT login, name, col_B, -- .... 
Dalam hal ini, satu cabang harus dibuat tergantung pada yang lain.

Kasus lain yang menarik adalah koreksi dalam migrasi.

Intinya adalah bahwa migrasi yang diterapkan tidak akan lagi diterapkan lagi, tidak peduli berapa banyak perubahan yang Anda lakukan untuk itu (Anda harus memutar kembali terlebih dahulu, dan kemudian menerapkannya lagi). Yaitu Anda mengirim Migrasi untuk pengujian, semua aturan, dan kemudian Anda menyadarinya dan melakukan edit kecil. Tetapi tes atau server lain tempat Anda menggunakannya tidak akan mengetahuinya.

Dalam kasus ini, kami mengganti nama file migrasi, menambahkan nomor versi baru, sehingga migrator mulai menafsirkan ini sebagai 2 perintah - gulung balik 1 dan gulung 2,
misalnya:



Kembalikan


Selalu tulis ROLLBACK, bahkan jika itu tidak dapat mengembalikan basis ke keadaan semula. Misalnya, DROP TABLE, ROLLBACK macam apa itu?

Dalam kasus seperti itu, kami menulis CREATE TABLE kosong. Intinya adalah bahwa sistem dev selalu dapat dengan mudah beralih antar cabang. Untuk PROD, manajemen revisi yang ireversibel sudah diputuskan pada tingkat yang berbeda. Saya bisa membuat salinan tabel, atau mengganti nama alih-alih menghapusnya. Tetapi prinsip penulisan migrasi - rollback DIPEROLEH untuk mengembalikan STRUKTUR pangkalan ke tingkat awal, dan data sudah mungkin.

Dalam lingkungan pertempuran, saya menggunakan kemunduran hanya 1-2 kali dalam hidup saya. Dan di dev sepanjang waktu. Oleh karena itu, saya selalu memeriksa bahwa rollback mengembalikan semuanya ke keadaan yang diinginkan.

Seringkali, pengembang dapat membuat kesalahan dalam rollback. Karena mereka terutama berkonsentrasi pada suntingan baru, mereka diuji dan bekerja dengannya. Orang lain dan proses sudah bekerja dengan rollback. Karenanya, saya selalu menguji migrasi UP - ROLLBACK - UP

Poin yang menarik muncul pada basis tes permanen (database tidak dihapus). Mereka menulis migrasi, rollback berfungsi dengan baik, mereka mengirimnya untuk pengujian, tester menghasilkan data dalam format baru, mencoba memutar kembali, tetapi mereka tidak memberikan data baru. Contoh klasik

 ALTER TABLE abc ALTER COLUMN code SET NULL 

Hebat! Setelah pengujian, database penuh dengan nilai NULL. Lakukan ROLLBACK:

 ALTER TABLE abc ALTER COLUMN code SET NOT NULL 

dan sebaliknya :-(

Anda perlu menambahkan perintah:

 DELETE FROM abc WHERE code IS NULL 

Kesulitannya adalah Anda perlu mengingat hal ini dan tidak mengotomatiskannya jika kita tidak berbicara tentang membuat kembali basis data dari awal setiap saat.

Sedikit tentang penghapusan data

Biasanya kami mencoba TIDAK menghapus tabel dan kolom yang diisi sekaligus. Lebih baik untuk mengubah nama atau membuat salinan, dan menghapusnya nanti, ketika semuanya beres dan data kehilangan relevansi:

 ALTER TABLE user_logs RENAME TO user_logs_20190223; --  CREATE TABLE user_logs_20190223 AS TABLE user_logs; 

Migrator


Kami sedang bekerja dengan Laravel sekarang - ia memiliki mesin manajemen migrasi standar yang dikenal. Jika Anda mau, tulislah bahkan dalam SQL murni, meskipun masih dalam kelas PHP. Tetapi upaya berulang saya untuk membuatnya bekerja seperti yang kami butuhkan menghasilkan repo yang terpisah:

  • Solusinya terdiri dari 2 bagian - lib dan implementasi untuk konsol tertentu (Laravel, Symfony). Anda dapat berintegrasi ke konsol mana pun, atau setidaknya di moncong web.
  • Tidak ada konfigurasi dan koneksi - mengapa, ketika sudah ada di proyek Anda. Tutup koneksi Anda ke antarmuka dan pergi.
  • Rollback SQL disimpan dalam database. Ini diperlukan untuk beralih antar cabang.
  • Diuji pada Postgesql, Mysql (tidak ada transaksi). Ini pada prinsipnya cocok untuk pangkalan dan struktur apa pun, karena format baku digunakan.


Referensi
- migrasi-lib
- implementasi di bawah Laravel / Artisan
- implementasi di bawah Symfony / Console

Source: https://habr.com/ru/post/id435438/


All Articles