Cara menggunakan GraphQL Federation untuk bermigrasi secara bertahap dari Monolith (Python) ke Microservices (Go)ย ย 

Atau bagaimana cara mengganti pondasi rumah tua agar tidak roboh







Sekitar 10 tahun yang lalu, kami memilih Python 2 untuk mengembangkan platform pembelajaran monolitik kami. Tetapi industri telah berubah secara dramatis sejak saat itu. Python 2 resmi dimakamkan pada 1 Januari 2020. Di artikel sebelumnya , kami menjelaskan mengapa kami memutuskan untuk tidak bermigrasi ke Python 3. 



Jutaan orang menggunakan platform kami setiap bulan. 



Kami mengambil risiko tertentu saat memutuskan untuk menulis ulang backend kami di Go dan mengubah arsitekturnya. 



Kami memilih Go karena beberapa alasan:



  1.  Kecepatan kompilasi tinggi.
  2. Menghemat RAM.
  3. Cukup banyak pilihan IDE dengan dukungan Go.


Tapi kami mengambil pendekatan yang meminimalkan risikonya.



Federasi GraphQL



Kami memutuskan untuk membangun arsitektur baru kami di sekitar GraphQL Apollo Federation . GraphQL dibuat oleh pengembang Facebook sebagai alternatif dari REST API. Federasi adalah tentang membangun gerbang tunggal untuk berbagai layanan. Setiap layanan dapat memiliki skema GraphQL-nya sendiri. Gerbang umum menggabungkan skema mereka, menghasilkan API tunggal, dan memungkinkan permintaan untuk beberapa layanan pada waktu yang sama. 



Sebelum kita melangkah lebih jauh, saya ingin menyoroti hal-hal berikut:



  1. Tidak seperti REST API, setiap server GraphQL memiliki skema data yang diketik sendiri. Ini memungkinkan Anda untuk mendapatkan kombinasi data apa pun dengan bidang arbitrer yang Anda butuhkan.



  2. Gateway REST API memungkinkan Anda mengirim permintaan hanya ke satu layanan backend; Gateway GraphQL menghasilkan rencana kueri untuk sejumlah layanan backend yang sewenang-wenang dan memungkinkan Anda mengembalikan pilihan dari mereka dalam satu respons umum.


Jadi, setelah memasukkan gateway GraphQL di sistem kami, kami mendapatkan sesuatu seperti ini:





URL gambar:     https://lh6.googleusercontent.com/6GBj9z5WVnQnhqI19oNTRncw0LYDJM4U7FpWeGxVMaZlP46IAIcKfYZKTtHcl-bDFomedAoxSa9pFo6pdhL2daxyWNX2ZKVQIgqIIBWHxnXEouzcQhO9_mdf1tODwtti5OEOOFeb 



Gateway (alias layanan graphql-gateway) bertanggung jawab untuk menciptakan dan mengirimkan rencana permintaan GraphQL-pertanyaan untuk layanan kami yang lain - tidak hanya monolit. Layanan Go kami memiliki skema GraphQL mereka sendiri. Kami menggunakan gqlgen (ini adalah pustaka GraphQL untuk Go) untuk menghasilkan tanggapan atas kueri



Karena Federasi GraphQL menyediakan skema GraphQL yang umum, dan gateway menggabungkan semua skema layanan individual menjadi satu, monolit kami akan berinteraksi dengannya seperti layanan lainnya. Ini adalah poin fundamental.



Selanjutnya, kita akan berbicara tentang bagaimana kita menyesuaikan server Apollo GraphQL untuk naik dengan aman dari monolit (Python) kita ke arsitektur layanan mikro (Go).



Pengujian berdampingan



GraphQL "berpikir" dengan kumpulan objek dan bidang jenis tertentu. Kode yang mengetahui apa yang harus dilakukan dengan permintaan masuk, bagaimana dan data apa yang akan diekstrak dari bidang disebut resolver. 



Mari pertimbangkan proses migrasi menggunakan contoh tipe data untuk tugas:



123 ketik Tugas {createDate: Time โ€ฆโ€ฆโ€ฆ.}


Jelas bahwa pada kenyataannya kami memiliki lebih banyak bidang, tetapi untuk setiap bidang semuanya akan terlihat sama.



Katakanlah kita ingin kolom monolit ini diwakili dalam layanan baru kita yang ditulis dalam Go. Bagaimana kita bisa yakin bahwa layanan baru sesuai permintaan akan mengembalikan data yang sama dengan monolit? Untuk melakukan ini, kami menggunakan pendekatan yang mirip dengan pustaka Scientist : kami meminta data dari monolit dan layanan baru, tetapi kemudian membandingkan hasilnya dan hanya mengembalikan salah satunya.



Langkah 1: mode manual



Ketika pengguna meminta nilai bidang createDate, gateway GraphQL kami pertama-tama mengakses monolit (yang ditulis dengan Python, ingat). 





Pada langkah pertama, kita perlu memastikan bahwa bidang tersebut dapat ditambahkan ke layanan tugas baru yang sudah ditulis di Go. File dengan ekstensi .graphql harus berisi kode penyelesai berikut:



12345 memperpanjang Jenis Penugasan kunci(bidang: "id") {id: ID! luar     CreatedDate: Time @migrate (dari: "python", state: "manual")}


Di sini kami menggunakan Federasi untuk mengatakan bahwa layanan menambahkan bidang CreatedDate ke tipe Tugas. Bidang ini diakses oleh id. Kami juga menambahkan "bahan rahasia" - perintah migrasi. Kami menulis kode yang memahami arahan ini dan membuat beberapa skema yang akan digunakan gateway GraphQL saat memutuskan apakah akan merutekan permintaan.



Dalam mode manual, permintaan hanya akan dialamatkan ke kode monolit. Kami harus mempertimbangkan kemungkinan ini saat mengembangkan layanan baru. Untuk mendapatkan nilai dari field createDate, kita masih bisa mengakses monolith secara langsung (dalam mode utama), atau kita bisa meng-query gateway GraphQL untuk skema dalam mode manual. Kedua opsi tersebut seharusnya berfungsi.



Langkah 2: mode berdampingan



Setelah kami menulis kode resolver untuk bidang createDate, kami mengalihkannya ke mode berdampingan:



12345 memperpanjang Jenis Penugasan kunci(bidang: "id") {id: ID! luar     CreatedDate: Time @migrate (dari: "python", state: "side-by-side")}


Dan sekarang gateway akan mengakses monolit (Python) dan layanan baru (Go). Ini akan membandingkan hasil, mencatat kasus di mana ada perbedaan, dan mengembalikan hasil dari monolit ke pengguna.



Mode ini benar-benar menanamkan kepercayaan yang tinggi bahwa sistem kami tidak akan bermasalah selama proses migrasi. Selama bertahun-tahun, jutaan pengguna dan "kiloton" data telah melalui frontend dan backend kami. Dengan mengamati cara kerja kode ini dalam kondisi nyata, kami dapat memastikan bahwa bahkan kasus yang jarang terjadi dan pencilan acak ditangkap dan kemudian diproses secara stabil dan benar.



Selama pengujian, kami menerima laporan seperti itu. 





Cobalah untuk memperbesar gambar ini selama tata letak entah bagaimana tanpa kehilangan kualitas yang kuat.



Mereka fokus pada kasus di mana ditemukan ketidaksesuaian dalam pengoperasian monolit dan layanan baru. 



Pada awalnya, kami sering menemui kasus seperti itu. Seiring waktu, kami telah belajar untuk mengidentifikasi masalah seperti itu, menilai kekritisannya dan, jika perlu, menghilangkannya.



Saat bekerja dengan server pengembang kami, kami menggunakan alat yang menyoroti perbedaan warna. Ini membuatnya lebih mudah untuk menganalisis masalah dan menguji solusi.



Bagaimana dengan mutasi?



Anda mungkin bertanya-tanya apakah kita menjalankan logika yang sama di Python dan Go, apa yang terjadi pada kode yang mengubah data, daripada hanya menanyakannya? Dalam istilah GraphQL, ini disebut mutasi.



Pengujian berdampingan kami tidak memperhitungkan mutasi. Kami melihat beberapa pendekatan untuk melakukan ini - ternyata lebih kompleks dari yang kami kira. Tetapi kami telah mengembangkan pendekatan yang membantu memecahkan masalah mutasi.



Langkah 2.5: mode kenari



Jika kita memiliki ladang atau mutasi yang berhasil bertahan ke tahap produksi, kita mengaktifkan mode kenari.



12345 memperpanjang Jenis Penugasan kunci(bidang: "id") {id: ID! luar     CreatedDate: Time @migrate (dari: "python", state: "canary")}


Bidang kenari dan mutasi akan ditambahkan ke layanan Go untuk sebagian kecil pengguna kami. Selain itu, pengguna internal platform sedang menguji skema canary. Ini adalah cara yang cukup aman untuk menguji perubahan kompleks. Kami dapat dengan cepat menonaktifkan sirkuit kenari jika ada yang tidak berfungsi seperti yang diharapkan.



Kami hanya menggunakan satu sirkuit kenari dalam satu waktu. Dalam praktiknya, tidak banyak bidang dan mutasi berada dalam mode kenari secara bersamaan. Jadi, saya rasa tidak akan ada masalah di masa depan. Ini adalah kompromi yang baik karena skemanya cukup besar (lebih dari 5000 bidang) dan instans gateway harus menyimpan tiga skema dalam memori - primer, manual, dan kenari.



Langkah 3: mode bermigrasi



Pada langkah ini, bidang createDate harus dalam mode migrasi:



12345 memperpanjang Jenis Penugasan kunci(bidang: "id") {id: ID! luar     CreatedDate: Time @migrate (dari: "python", state: "migrated")}


Dalam mode ini, gateway GraphQL hanya mengirim permintaan ke layanan baru yang ditulis dalam Go. Tetapi kapan saja kita dapat melihat bagaimana monolit akan memproses permintaan yang sama. Ini membuatnya lebih mudah untuk menerapkan dan mengembalikan perubahan jika terjadi kesalahan.



Langkah 4: Menyelesaikan migrasi



Setelah penerapan berhasil, kami tidak lagi memerlukan kode monolit untuk bidang ini, dan kami menghapus perintah @migrate dari kode resolver:



12345 memperpanjang Jenis Penugasan kunci(bidang: "id") {id: ID! luar     CreatedDate: Time}


Mulai sekarang, gateway akan menafsirkan ekspresi Assignment.createdDate sebagai mendapatkan nilai kolom dari layanan baru yang ditulis di Go.



Beginilah migrasi inkremental!



Dan seberapa jauh kita telah pergi?



Kami menyelesaikan infrastruktur pengujian berdampingan kami hanya tahun ini. Ini memungkinkan kami untuk menulis ulang sekumpulan kode Go dengan aman, perlahan tapi pasti. Sepanjang tahun, kami telah mempertahankan ketersediaan platform yang tinggi dengan latar belakang pertumbuhan lalu lintas di sistem kami. Pada saat penulisan ini, ~ 40% dari bidang GraphQL kami dipindahkan ke layanan Go. Jadi, pendekatan yang kami gambarkan bekerja dengan baik dalam proses migrasi.



Bahkan setelah proyek selesai, kami dapat terus menggunakan pendekatan ini untuk tugas-tugas lain yang terkait dengan perubahan arsitektur kami.



PS Steve Coffman memberikan ceramah tentang topik ini (di Google Open Source Live ). Anda bisa menonton rekamannyapembicaraan YouTube ini (atau hanya menonton presentasi ).






Server cloud dari Macleod cepat dan aman.



Daftar menggunakan tautan di atas atau dengan mengklik spanduk dan dapatkan diskon 10% untuk bulan pertama menyewa server dengan konfigurasi apa pun!






All Articles