Blog

๐Ÿค” Haruskah GraphQL Berbeda untuk Pengguna yang Berbeda?

Leonardo Losoviz
Oleh Leonardo Losoviz ยท

GraphQL adalah antarmuka untuk mengambil data dari suatu sumber, dengan spec GraphQL mendefinisikan persyaratan untuk antarmuka tersebut. Selama persyaratan ini terpenuhi, GraphQL tidak peduli bagaimana hal itu dicapai. Server GraphQL kemudian dapat diimplementasikan dalam JavaScript menggunakan promises, menggunakan arsitektur konkuren berbasis Golang, dipetakan ke file Excel, atau apa pun, dan semua ini bisa menjadi implementasi yang valid dari spec GraphQL.

GraphQL berada di antara klien dan layanan backend

Bagaimana mesin server diimplementasikan tidak penting untuk keberhasilan eksekusi permintaan GraphQL, karena interaksi antara klien dan server selalu sama, terjadi dengan mengirimkan query GraphQL menggunakan sintaks yang ditentukan, dan mendapatkan respons yang sesuai dalam format JSON.

Sekarang, ketika saya mengatakan bahwa implementasinya tidak penting, saya maksudkan dari perspektif pengguna API, yang hanya bermaksud mendapatkan data dari server. Bagaimana data yang dikembalikan diproduksi tidak menjadi perhatian.

Namun situasinya berubah bagi pengembang sisi server yang bekerja pada API, di mana detail implementasi memang sangat penting. Jika saya mengkode API GraphQL saya di PHP, maka saya akan berusaha semaksimal mungkin agar API saya diselesaikan seefisien mungkin, dan memiliki desain arsitektur yang seelegan mungkin, menggunakan kemampuan yang ditawarkan oleh PHP.

PHP vs Java vs JavaScript

Kemudian, kita memiliki kemungkinan konflik kepentingan antara kebutuhan untuk melindungi API, dan kemampuan yang diharapkan oleh para pengembang yang bekerja pada API, yang tidak ingin fitur yang didukung oleh bahasa yang mendasarinya diambil dari mereka (seperti kemampuan untuk mengeksekusi kode rekursif).

Konflik ini menjadi nyata dalam issue #929: Allow recursive references in fragments, yang berpendapat bahwa GraphQL seharusnya tidak melarang rekursi dalam fragments.

Dalam sebuah meetup lalu dari kelompok kerja GraphQL, Roman, yang merupakan pengembang yang mengangkat issue ini, mengungkapkan mengapa ia tidak setuju dengan batasan yang diberlakukan oleh spec:

Saya adalah pengembang sisi server, dan saya merasa spec terlalu banyak berbicara tentang eksekusi sisi server, padahal seharusnya berfokus pada apa yang ingin dikirimkan kepada klien - bukan bagaimana caranya

Aturan yang melarang rekursi dalam fragments telah dibenarkan atas dasar menjaga keamanan API publik. Lagipula, GraphQL dibuat oleh Facebook untuk mengirimkan data ke aplikasi publik mereka, dan pengguna tidak boleh dapat mengeksploitasi kelemahan dalam desain API yang dapat merusak layanan.

Pencipta GraphQL, Lee Byron, mengungkapkan tiga kekhawatiran utama:

rekursi tak terbatas; batasan tidak hanya berupa spesifikasi - bagaimana harus berhenti dan kapan

validasi data; mengembalikan nilai yang sama berkali-kali, bagaimana hal itu direpresentasikan dalam data. Idealnya Anda ingin mendeteksi bahwa itu bersiklus dan berhenti segera, tetapi beberapa server tidak dapat mendeteksi ini dan mungkin melakukan loop berkali-kali sebelum mereka mendeteksi ada yang salah dan berhenti

apa biaya tidak memiliki ini; apakah itu membenarkan masalah-masalah ini? Tidak; selalu mungkin bagi Anda untuk menentukan jumlah level kedalaman dalam query Anda - itulah versi yang didesugared dari apa yang akan kami lakukan jika kami menangani ini dalam GraphQL

Datang dari perspektif mereka sendiri, baik Roman maupun Lee ada benarnya. Lee Byron khawatir tentang keamanan API GraphQL publik. Menghindari fragments rekursif dibenarkan untuk memastikan bahwa tidak ada aktor jahat yang dapat meruntuhkan sistem dengan mengeksekusi loop siklus tanpa henti dalam query, dan bahkan menghilangkan kemungkinan tim "self-DDoSing", yang bisa terjadi jika mereka secara tidak sengaja mempublikasikan query yang menghentikan sistem.

Roman, bagaimanapun, khawatir dengan batasan terhadap kemampuannya sendiri untuk membuat API GraphQL. Karena Roman mungkin satu-satunya konsumen API-nya (yaitu API privat yang tidak terekspos ke pengguna), atau karena servernya mungkin memiliki kemampuan untuk mendeteksi dan menghentikan siklus yang berulang, maka ia percaya bahwa batasan GraphQL merugikan dan tidak dapat dibenarkan.

Pada inti diskusi, masalahnya bukan apakah fragments rekursif seharusnya diizinkan atau tidak, tetapi sesuatu yang lebih mendasar: Siapa target GraphQL? Jika bukan satu kelompok, dapatkah satu spesifikasi API memenuhi persyaratan dari semua pemangku kepentingan yang berbeda? Dan jika konflik tidak dapat dicegah, dapatkah setidaknya diperbaiki dengan cara tertentu?

Mari kita jelajahi pertanyaan-pertanyaan ini.

Siapa Target GraphQL?

GraphQL digunakan oleh berbagai jenis pemangku kepentingan, di antaranya kita dapat mengidentifikasi:

1. Pengguna API: Mereka yang mengonsumsi data dari suatu endpoint GraphQL, dengan alasan apa pun. Misalnya, kita semua dapat menjadi pengguna API dari API GraphQL publik GitHub, untuk mengambil data terkait repo GitHub kita.

2. Pengembang sisi klien: Mereka yang membuat aplikasi sisi klien yang didukung oleh suatu endpoint GraphQL. Misalnya, pengembang yang membangun situs dengan Gatsby mengandalkan GraphQL untuk mengambil konten situs.

3. Pengembang backend: Mereka yang membuat resolver untuk API GraphQL.

Selain itu, kita perlu mencatat bahwa API GraphQL bisa bersifat publik atau privat:

API Publik: Karena siapa pun memiliki akses ke endpoint GraphQL, kita harus memperhatikan langkah-langkah keamanan untuk menghindari serangan oleh aktor jahat.

API Privat: Karena hanya aktor yang dituju yang diberikan akses ke API, tidak ada risiko keamanan yang inheren, dan self-DDoSing dapat dengan mudah dihindari dengan praktik pengkodean yang baik.

Apakah Satu Spesifikasi API Memenuhi Persyaratan Semua Pemangku Kepentingan?

Issue yang diangkat oleh Roman dapat diinterpretasikan seperti ini: "Jika API GraphQL saya bersifat privat, dan saya tahu persis apa yang saya lakukan (dengan kepastian 100% bahwa kode saya akan berfungsi seperti yang diharapkan dan tidak ada eksekusi yang berhenti yang akan dihasilkan), lalu mengapa saya tidak bisa menggunakan rekursi dalam fragments?"

Rekursi @ xkcd

Contoh situasi ini terjadi setiap kali kita menggunakan framework bertenaga GraphQL untuk membangun situs statis (seperti Gatsby, Next.js atau RedwoodJS), karena API GraphQL sering kali bersifat privat, dan kita tidak dapat secara tidak sengaja melakukan DDoS pada aplikasi kita dan mengalami konsekuensi yang merugikan (paling buruk akan crash saat membangun situs statis di lingkungan pengembangan atau staging).

Para pengembang yang menggunakan pengaturan di atas mungkin bertanya-tanya mengapa spec GraphQL melarang mereka menggunakan fitur-fitur yang bermanfaat, yang tidak memiliki konsekuensi merugikan sama sekali untuk pengaturan mereka.

Kesimpulannya, dengan melarang fragments rekursif, spec GraphQL memberlakukan langkah keamanan yang berlaku untuk sebagian dari semua potensi penggunaan GraphQL, bukan semuanya, agar berada di sisi yang aman.

Bisakah Spec GraphQL Lebih Memuaskan Semua Pemangku Kepentingan?

Jika pemangku kepentingan yang berbeda memiliki persyaratan yang berbeda, bagaimana spec GraphQL dapat memuaskan semuanya? (Idenya adalah menghindari forking spec dan menghasilkan versi yang disesuaikan untuk target tertentu.)

Mari kita jelajahi beberapa ide, di mana yang pertama perlu melalui proses kontribusi spec, sementara yang kedua tidak.

Feature-toggle di Level Spec GraphQL

Satu kemungkinan jalur yang bisa diambil adalah membuat spec "menyarankan" tetapi tidak "memaksakan" aturan. Dalam hal ini, aturan yang melarang rekursi dalam fragments bisa sangat disarankan, tetapi fitur tersebut tetap akan diterima.

Sekarang, solusi ini akan mengubah kondisi default fragments rekursif dari "wajib" menjadi "opsional", yang akan menghasilkan dua konsekuensi negatif:

  • API akan tidak aman secara default (skenario yang ingin dihindari Lee Byron)
  • Ini akan menghasilkan breaking change, karena query yang dilarang kemudian akan diizinkan

Kemudian, akan lebih baik untuk membalik opsi tersebut, dengan tetap melarang rekursi dalam fragments secara default tetapi memberikan kemungkinan untuk mengaktifkan feature-flag yang menonaktifkan perilaku ini. Karena fitur harus dinonaktifkan secara eksplisit, hanya akan dilakukan oleh admin yang tahu apa yang mereka lakukan.

Karena fitur ini paling berharga di bawah pengaturan tertentu, server dan framework GraphQL dapat memutuskan apakah/bagaimana/kapan menawarkan konfigurasi tersebut. Misalnya, Gatsby dapat menampilkan opsi secara menonjol melalui beberapa UI saat membuat situs statis, dan menyembunyikannya jika tidak.

Ide umumnya adalah agar spec GraphQL mendukung "fitur yang diaktifkan tetapi opsional", yang dapat diaktifkan/dinonaktifkan melalui konfigurasi, dan status defaultnya adalah yang sudah dimiliki dalam spec.

Melarang fragments rekursif akan menjadi salah satunya, dan mungkin ada fitur lain seperti itu juga, seperti tipe Map, yang tidak diterima untuk spec oleh Lee Byron karena:

Ada trade-off yang signifikan untuk tipe Map vs daftar pasangan kunci/nilai. Satu masalah adalah melakukan paginasi atas koleksi. Daftar nilai dapat memiliki aturan paginasi yang jelas sementara Maps yang sering memiliki pasangan kunci-nilai yang tidak terurut jauh lebih sulit untuk dipaginasi.

Masalah lain adalah penggunaan. Paling sering Map digunakan dalam API di mana satu field dari nilai sedang diindeks, yang menurut saya adalah anti-pattern API karena pengindeksan adalah masalah penyimpanan dan masalah caching klien tetapi bukan masalah transport. Anti-pattern ini membuat saya khawatir. Meskipun ada beberapa penggunaan yang baik untuk Maps dalam API, saya khawatir bahwa penggunaan umum akan untuk anti-pattern ini sehingga saya menyarankan untuk melanjutkan dengan hati-hati.

Lee Byron mengungkapkan kekhawatirannya bahwa fitur tersebut akan digunakan sebagai anti-pattern. Namun, ia juga mengakui bahwa ada penggunaan yang baik untuknya. Kemudian, karena issue ini mendapat banyak dukungan dari komunitas (dengan lebih dari 150 ๐Ÿ‘), para pengembang dapat diberikan opsi untuk secara eksplisit mengaktifkan penambahan tipe Map ke skema mereka, dan menghadapi konsekuensinya.

Feature-toggle oleh Server GraphQL

Jika proposal di atas tidak mendapat dukungan karena terlalu berisiko untuk spec GraphQL, alternatifnya adalah mengimplementasikannya di level server GraphQL. Kemudian, server GraphQL dapat menyediakan fitur kustom yang menonaktifkan rekursi dalam fragments.

Menggeneralisasi ide tersebut, server GraphQL dapat menawarkan untuk menonaktifkan fitur-fitur tertentu dari spec, dan mengaktifkan fitur lain yang tidak ada dalam spec. Agar perilaku ini tidak menghasilkan kejutan, server harus memastikan bahwa status default adalah yang diperlukan oleh spec, dan admin API harus sepenuhnya menyadari konsekuensi dari mengaktifkan fitur tersebut. (Ini adalah strategi yang diikuti oleh Gato GraphQL untuk "innovative features"-nya.)

Penutup

Seiring GraphQL semakin populer, framework-framework baru yang mendukung kemampuan baru telah menjadikannya bagian dari stack mereka, dan pemangku kepentingan baru (dan jenis baru dari mereka) telah terlibat. Kemudian, sebuah spesifikasi yang awalnya dibuat oleh Facebook untuk mendefinisikan bagaimana aplikasinya akan mendapatkan data dari servernya perlu semakin berhadapan dengan lebih banyak kasus penggunaan.

Tidak bisa dihindari adanya konflik, di mana sekelompok pemangku kepentingan membutuhkan fitur yang kontraproduktif, atau bahkan merugikan, bagi pemangku kepentingan lain, seperti halnya fragments rekursif. Apa yang dapat dilakukan untuk memperbaiki situasi, dan menghindari pemangku kepentingan yang tidak puas menjadi kecewa dengan GraphQL?

Saya berpendapat bahwa spec dapat menawarkan kesempatan untuk "menonaktifkan" sebuah fitur, memungkinkan admin yang tahu apa yang mereka lakukan untuk menghapus beberapa batasan guna memenuhi persyaratan mereka sendiri. Sekarang, saya sendiri tidak setuju dengan solusi ini, tetapi saya tetap memunculkannya karena diskusi ini perlu dilakukan. Karena ide ini kontroversial, alternatif yang lebih baik adalah agar server GraphQL menyediakan perilaku ini melalui fitur kustom, yang harus diaktifkan secara eksplisit.


Berlangganan newsletter kami

Tetap update dengan semua pembaruan Gato GraphQL.