A GraphQL Primer: Mengapa Kami Membutuhkan Jenis API Baru (Bagian 1)
Diterbitkan: 2022-03-10Dalam seri ini, saya ingin memperkenalkan Anda pada GraphQL. Pada akhirnya, Anda harus memahami tidak hanya apa itu, tetapi juga asal-usulnya, kekurangannya, dan dasar-dasar cara bekerja dengannya. Dalam artikel pertama ini, daripada melompat ke implementasi, saya ingin membahas bagaimana dan mengapa kami tiba di GraphQL (dan alat serupa) dengan melihat pelajaran yang dipetik dari 60 tahun terakhir pengembangan API, dari RPC hingga sekarang. Lagi pula, seperti yang digambarkan Mark Twain dengan penuh warna, tidak ada ide baru.
"Tidak ada yang namanya ide baru. Tidak mungkin. Kami hanya mengambil banyak ide lama dan memasukkannya ke dalam semacam kaleidoskop mental."
— Mark Twain dalam "Otobiografi Mark Twain Sendiri: Bab-bab Dari Ulasan Amerika Utara"
Tapi pertama-tama, saya harus berbicara dengan gajah di dalam ruangan. Hal-hal baru selalu mengasyikkan, tetapi juga bisa terasa melelahkan. Anda mungkin pernah mendengar tentang GraphQL dan hanya berpikir: “Mengapa…” Atau, mungkin Anda berpikir sesuatu seperti, “Mengapa saya peduli dengan tren desain API baru? REST itu… baik-baik saja.” Ini adalah pertanyaan yang sah jadi izinkan saya membantu menjelaskan mengapa Anda harus memperhatikan yang satu ini.
pengantar
Manfaat membawa alat baru ke tim Anda harus dipertimbangkan dengan biayanya. Ada banyak hal yang harus diukur. Ada waktu yang dibutuhkan untuk belajar, waktu yang dibutuhkan untuk mengkonversi dari pengembangan fitur, biaya pemeliharaan dua sistem. Dengan biaya tinggi seperti itu, teknologi baru apa pun harus lebih baik, lebih cepat, atau lebih produktif dengan jumlah yang sangat besar. Peningkatan bertahap, meskipun mengasyikkan, tidak sepadan dengan investasinya. Jenis API yang ingin saya bicarakan, khususnya GraphQL, menurut saya merupakan langkah maju yang besar dan memberikan lebih dari cukup manfaat untuk membenarkan biaya.
Daripada menjelajahi fitur terlebih dahulu, akan sangat membantu untuk menempatkannya ke dalam konteks dan memahami bagaimana fitur itu ada. Untuk melakukan ini, saya akan mulai dengan sedikit rekap sejarah API.
RPC
RPC adalah, bisa dibilang pola API utama pertama dan asal-usulnya kembali ke komputasi awal di pertengahan 60-an. Pada saat itu, komputer masih begitu besar dan mahal sehingga gagasan pengembangan aplikasi berbasis API, seperti yang kita pikirkan, sebagian besar hanya teoretis. Kendala seperti bandwidth/latensi, daya komputasi, waktu komputasi bersama, dan kedekatan fisik memaksa para insinyur untuk berpikir dalam kerangka sistem terdistribusi daripada layanan yang mengekspos data. Dari ARPANET di tahun 60-an, hingga pertengahan 90-an dengan hal-hal seperti CORBA dan RMI Java, sebagian besar komputer berinteraksi satu sama lain menggunakan Remote Procedure Calls (RPC) yang merupakan model interaksi klien-server di mana klien menyebabkan suatu prosedur (atau metode) untuk dijalankan pada server jauh.
Ada banyak hal bagus tentang RPC. Prinsip utamanya adalah memungkinkan pengembang untuk memperlakukan kode di lingkungan yang jauh seolah-olah berada di lingkungan lokal, meskipun jauh lebih lambat dan kurang dapat diandalkan yang menciptakan kontinuitas dalam sistem yang berbeda dan berbeda. Seperti banyak hal yang keluar dari ARPANET, ini lebih maju dari waktunya karena jenis kontinuitas ini adalah sesuatu yang masih kami perjuangkan ketika bekerja dengan tindakan yang tidak dapat diandalkan dan tidak sinkron seperti akses DB dan panggilan layanan eksternal.
Selama beberapa dekade, ada sejumlah besar penelitian tentang bagaimana memungkinkan pengembang untuk menanamkan perilaku asinkron seperti ini ke dalam aliran program yang khas; seandainya ada hal-hal seperti Promises, Futures, dan ScheduledTasks yang tersedia pada saat itu, ada kemungkinan lanskap API kami akan terlihat berbeda.
Hal hebat lainnya tentang RPC adalah karena RPC tidak dibatasi oleh struktur data, metode yang sangat khusus dapat ditulis untuk klien yang meminta dan mengambil informasi yang dibutuhkan secara tepat yang dapat menghasilkan overhead jaringan yang minimal dan muatan yang lebih kecil.
Namun, ada hal-hal yang membuat RPC sulit. Pertama, kontinuitas membutuhkan konteks . RPC, secara desain, menciptakan cukup banyak sambungan antara sistem lokal dan jarak jauh — Anda kehilangan batas antara kode lokal dan jarak jauh Anda. Untuk beberapa domain, ini boleh saja atau bahkan lebih disukai seperti di SDK klien, tetapi untuk API di mana kode klien tidak dipahami dengan baik, ini bisa jadi kurang fleksibel daripada sesuatu yang lebih berorientasi pada data.
Lebih penting lagi, adalah potensi proliferasi metode API . Secara teori, layanan RPC memperlihatkan API kecil yang bijaksana yang dapat menangani tugas apa pun. Dalam praktiknya, sejumlah besar titik akhir eksternal dapat bertambah tanpa banyak struktur. Dibutuhkan disiplin yang luar biasa untuk mencegah API yang tumpang tindih dan duplikasi dari waktu ke waktu saat anggota tim datang dan pergi dan proyek berputar.
Memang benar bahwa dengan perubahan perkakas dan dokumentasi yang tepat, seperti yang saya sebutkan, dapat dikelola tetapi dalam waktu saya menulis perangkat lunak, saya telah menemukan sedikit layanan pendokumentasian otomatis dan disiplin, jadi, bagi saya, ini sedikit pengalih perhatian.
SABUN MANDI
Jenis API utama berikutnya yang muncul adalah SOAP, yang lahir pada akhir 90-an di Microsoft Research. SOAP ( S imple Object A ccess P rotocol ) adalah spesifikasi protokol yang ambisius untuk komunikasi berbasis XML antar aplikasi. Ambisi SOAP yang dinyatakan adalah untuk mengatasi beberapa kelemahan praktis dari RPC, XML-RPC khususnya, dengan menciptakan fondasi yang terstruktur dengan baik untuk layanan web yang kompleks. Akibatnya, ini hanya berarti menambahkan sistem tipe perilaku ke XML. Sayangnya, itu menciptakan lebih banyak hambatan daripada yang dipecahkan sebagaimana dibuktikan oleh fakta bahwa sangat sedikit titik akhir SOAP baru yang ditulis hari ini.
"SOAP adalah apa yang kebanyakan orang anggap sebagai kesuksesan moderat."
— Kotak Don
SOAP memang memiliki beberapa hal baik untuk itu meskipun verbositasnya yang tak tertahankan dan nama-nama yang mengerikan. Kontrak yang dapat diterapkan di WSDL dan WADL (diucapkan "wizdle" dan "waddle") antara klien dan server dijamin dapat diprediksi, hasil yang aman, dan WSDL dapat digunakan untuk menghasilkan dokumentasi atau untuk membuat integrasi dengan IDE dan alat lainnya.
Pengungkapan besar SOAP mengenai evolusi API adalah pengenalan panggilan yang lebih berorientasi sumber daya secara bertahap dan mungkin tidak disengaja. Titik akhir SOAP memungkinkan Anda untuk meminta data dengan struktur yang telah ditentukan daripada memikirkan metode yang diperlukan untuk menghasilkan data (dengan asumsi itu ditulis dengan cara ini).
Kelemahan paling signifikan dari SOAP adalah menjadi sangat bertele-tele; hampir tidak mungkin untuk digunakan tanpa banyak perkakas . Anda memerlukan perkakas untuk menulis tes, perkakas untuk memeriksa respons dari server, dan perkakas untuk mengurai semua data. Banyak sistem lama masih menggunakan SOAP, tetapi persyaratan alat membuatnya terlalu rumit untuk sebagian besar proyek baru, dan jumlah byte yang diperlukan untuk struktur XML menjadikannya pilihan yang buruk untuk melayani perangkat seluler atau sistem terdistribusi yang cerewet.
Untuk informasi lebih lanjut, ada baiknya membaca spesifikasi SOAP serta sejarah SOAP yang sangat menarik dari Don Box, salah satu anggota tim asli.
ISTIRAHAT
Akhirnya, kita sampai pada pola desain API du jour: REST. REST, yang diperkenalkan dalam disertasi doktoral oleh Roy Fielding pada tahun 2000, mengayunkan pendulum ke arah yang sama sekali berbeda. REST adalah, dalam banyak hal, antitesis dari SOAP dan melihat mereka berdampingan membuat Anda merasa seperti disertasinya sedikit berhenti.
SOAP menggunakan HTTP sebagai transportasi bodoh dan membangun strukturnya di badan permintaan dan tanggapan. REST, di sisi lain, membuang kontrak klien-server, perkakas, XML, dan header yang dipesan lebih dahulu, menggantinya dengan semantik HTTP karena ini adalah pemilihan struktur alih-alih menggunakan kata kerja HTTP yang berinteraksi dengan data dan URI yang mereferensikan sumber daya dalam beberapa hierarki data.
SABUN MANDI | ISTIRAHAT | |
---|---|---|
Kata Kerja HTTP | DAPATKAN, PUT, POSTING, PATCH, DELETE | |
Format data | XML | Apapun yang kamu mau |
Kontrak Klien/Server | Sepanjang hari setiap hari! | Siapa yang butuh itu? |
Ketik Sistem | JavaScript memiliki unsigned pendek kan? | |
URL | Jelaskan operasi | Sumber daya bernama |
REST sepenuhnya dan secara eksplisit mengubah desain API dari interaksi pemodelan menjadi hanya memodelkan data domain. Menjadi sepenuhnya berorientasi pada sumber daya saat bekerja dengan REST API, Anda tidak perlu lagi tahu, atau peduli, apa yang diperlukan untuk mengambil bagian data tertentu; Anda juga tidak perlu mengetahui apa pun tentang penerapan layanan backend.

Kesederhanaan bukan hanya keuntungan bagi pengembang, tetapi karena URL mewakili informasi yang stabil, ia mudah di-cache, statelessness-nya memudahkan untuk menskalakan secara horizontal dan, karena memodelkan data daripada mengantisipasi kebutuhan konsumen, ini dapat secara dramatis mengurangi luas permukaan API. .
REST hebat, dan keberadaannya di mana-mana adalah kesuksesan yang mencengangkan, tetapi, seperti semua solusi yang datang sebelumnya, REST bukannya tanpa kekurangannya. Untuk berbicara secara konkret tentang beberapa kekurangannya, mari kita lihat contoh dasar. Mari kita berpura-pura bahwa kita harus membangun halaman arahan blog yang menampilkan daftar posting blog dan nama penulisnya.

Mari kita tulis kode yang dapat mengambil data beranda dari REST API biasa. Kami akan mulai dengan beberapa fungsi yang membungkus sumber daya kami.
const getPosts = () => fetch(`${API_ROOT}/posts`); const getPost = postId => fetch(`${API_ROOT}/posts/${postId}`); const getAuthor = authorId => fetch(`${API_ROOT}/authors/${authorId}`);
Sekarang, mari kita mengatur!
const getPostWithAuthor = postId => { return getPost(postId) .then(post => getAuthor(post.author)) .then(author => { return Object.assign({}, post, { author }) }) }; const getHomePageData = () => { return getPosts() .then(postIds => { const postDetails = postIds.map(getPostWithAuthor); return Promise.all(postDetails); }) };
Jadi kode kita akan melakukan hal berikut:
- Ambil semua Postingan;
- Ambil detail tentang setiap Postingan;
- Ambil sumber daya Penulis untuk setiap Postingan.
Hal yang menyenangkan adalah bahwa ini cukup mudah untuk dipikirkan, diatur dengan baik dan batas-batas konseptual dari setiap sumber daya digambar dengan baik. Yang mengecewakan di sini adalah kami baru saja membuat delapan permintaan jaringan, banyak di antaranya terjadi secara serial.
GET /posts GET /posts/234 GET /posts/456 GET /posts/17 GET /posts/156 GET /author/9 GET /author/4 GET /author/7 GET /author/2
Ya, Anda dapat mengkritik contoh ini dengan menyarankan bahwa API dapat memiliki titik akhir /posts
yang diberi halaman tetapi itu adalah rambut yang membelah. Faktanya tetap bahwa Anda sering memiliki kumpulan panggilan API yang bergantung satu sama lain untuk merender aplikasi atau halaman yang lengkap.
Mengembangkan klien dan server REST tentu saja lebih baik daripada sebelumnya, atau setidaknya lebih banyak bukti bodoh, tetapi banyak yang telah berubah dalam dua dekade sejak makalah Fielding. Pada saat itu, semua komputer adalah plastik krem; sekarang mereka aluminium! Serius, 2000 mendekati puncak ledakan dalam komputasi pribadi. Setiap tahun prosesor berlipat ganda dalam kecepatan, dan jaringan menjadi lebih cepat dengan kecepatan yang luar biasa. Penetrasi pasar internet adalah sekitar 45% tanpa tujuan selain naik.
Kemudian, sekitar tahun 2008, komputasi seluler menjadi arus utama. Dengan seluler, kami secara efektif mengalami kemunduran satu dekade dalam hal kecepatan/kinerja dalam semalam. Pada tahun 2017, kami memiliki hampir 80% penetrasi smartphone domestik dan lebih dari 50% global, dan inilah saatnya untuk memikirkan kembali beberapa asumsi kami tentang desain API.
Kelemahan REST
Berikut ini adalah tinjauan kritis REST dari perspektif pengembang aplikasi klien, terutama yang bekerja di seluler. GraphQL dan GraphQL-style API bukanlah hal baru dan tidak memecahkan masalah yang berada di luar jangkauan pengembang REST. Kontribusi GraphQL yang paling signifikan adalah kemampuannya untuk memecahkan masalah ini secara sistematis dan dengan tingkat integrasi yang tidak tersedia di tempat lain. Dengan kata lain, ini adalah solusi "termasuk baterai".
Penulis utama REST, termasuk Fielding, menerbitkan sebuah makalah pada akhir 2017 (Refleksi pada Gaya Arsitektur REST dan “Desain Berprinsip Arsitektur Web Modern”) yang merefleksikan dua dekade REST dan banyak pola yang telah diilhaminya. Ini singkat dan benar-benar layak dibaca bagi siapa saja yang tertarik dengan desain API.
Dengan beberapa konteks historis dan aplikasi referensi, mari kita lihat tiga kelemahan utama REST.
REST itu cerewet
Layanan REST cenderung setidaknya agak "cerewet" karena dibutuhkan beberapa perjalanan bolak-balik antara klien dan server untuk mendapatkan data yang cukup untuk membuat aplikasi. Rangkaian permintaan ini memiliki dampak kinerja yang buruk, terutama pada perangkat seluler. Kembali ke contoh blog, bahkan dalam skenario terbaik dengan ponsel baru dan jaringan andal dengan koneksi 4G, Anda telah menghabiskan hampir 0,5 detik hanya untuk overhead latensi sebelum byte data pertama diunduh.
55ms 4G latency * 8 permintaan = 440ms overhead

Masalah lain dengan layanan cerewet adalah bahwa dalam banyak kasus dibutuhkan lebih sedikit waktu untuk mengunduh satu permintaan besar daripada banyak permintaan kecil. Penurunan kinerja permintaan kecil adalah benar karena berbagai alasan termasuk Mulai Lambat TCP, kurangnya kompresi header dan efisiensi gzip dan jika Anda ingin tahu tentangnya, saya sangat merekomendasikan membaca Jaringan Peramban Berkinerja Tinggi Ilya Grigorik. Blog MaxCDN juga memiliki ikhtisar yang bagus.
Masalah ini tidak secara teknis dengan REST tetapi dengan HTTP, khususnya HTTP/1. HTTP/2 semuanya memecahkan masalah obrolan terlepas dari gaya API, dan memiliki dukungan luas di klien seperti browser dan SDK asli. Sayangnya, peluncurannya lambat di sisi API. Di antara 10k situs web teratas, adopsi sekitar 20% (dan terus meningkat) pada akhir 2017. Bahkan Node.js, yang sangat mengejutkan saya, mendapat dukungan HTTP/2 dalam rilis 8.x mereka. Jika Anda memiliki kemampuan, perbarui infrastruktur Anda! Sementara itu, jangan berlarut-larut karena ini hanya satu bagian dari persamaan.
Selain HTTP, bagian terakhir mengapa obrolan itu penting, berkaitan dengan cara perangkat seluler, dan khususnya radio mereka bekerja. Panjang dan pendeknya adalah bahwa mengoperasikan radio adalah salah satu bagian telepon yang paling intensif baterai sehingga OS mematikannya di setiap kesempatan. Memulai radio tidak hanya menguras baterai tetapi juga menambahkan lebih banyak biaya tambahan untuk setiap permintaan.
TMI (Berlebihan)
Masalah berikutnya dengan layanan gaya REST adalah bahwa cara mengirim lebih banyak informasi daripada yang dibutuhkan. Dalam contoh blog kami, yang kami butuhkan hanyalah judul setiap posting dan nama penulisnya yang hanya sekitar 17% dari apa yang dikembalikan. Itu adalah kerugian 6x untuk muatan yang sangat sederhana. Dalam API dunia nyata, overhead semacam itu bisa sangat besar. Situs e-niaga, misalnya, sering mewakili satu produk sebagai ribuan baris JSON. Seperti masalah obrolan, layanan REST dapat menangani skenario ini hari ini menggunakan "sparse fieldsets" untuk menyertakan atau mengecualikan bagian data secara kondisional. Sayangnya, dukungan untuk ini tidak sempurna, tidak lengkap, atau bermasalah untuk cache jaringan.
Perkakas Dan Introspeksi
Hal terakhir yang tidak dimiliki REST API adalah mekanisme untuk introspeksi. Tanpa kontrak apa pun dengan informasi tentang jenis pengembalian atau struktur titik akhir, tidak ada cara untuk menghasilkan dokumentasi, membuat perkakas, atau berinteraksi dengan data dengan andal. Dimungkinkan untuk bekerja di dalam REST untuk menyelesaikan masalah ini hingga tingkat yang berbeda-beda. Proyek yang sepenuhnya mengimplementasikan OpenAPI, OData, atau JSON API sering kali bersih, terspesifikasi dengan baik dan, pada berbagai tingkatan, didokumentasikan dengan baik tetapi backend seperti ini jarang terjadi. Bahkan Hypermedia, buah gantung yang relatif rendah, meskipun telah disebut-sebut dalam pembicaraan konferensi selama beberapa dekade, masih tidak sering dilakukan dengan baik, jika sama sekali.
Kesimpulan
Setiap tipe API memiliki kekurangan, tetapi setiap pola memiliki kekurangan. Tulisan ini bukanlah penilaian dari dasar fenomenal yang telah diletakkan oleh raksasa dalam perangkat lunak, hanya untuk penilaian yang bijaksana dari masing-masing pola ini, yang diterapkan dalam bentuk "murni" mereka dari perspektif pengembang klien. Saya berharap bahwa alih-alih keluar dari pemikiran ini, pola seperti REST atau RPC rusak, Anda dapat berpikir tentang bagaimana mereka masing-masing membuat pengorbanan dan area di mana organisasi teknik dapat memfokuskan upayanya untuk meningkatkan API-nya sendiri .
Pada artikel berikutnya, saya akan mengeksplorasi GraphQL dan bagaimana GraphQL bertujuan untuk mengatasi beberapa masalah yang saya sebutkan di atas. Inovasi dalam GraphQL dan alat serupa ada di tingkat integrasinya dan bukan dalam implementasinya. Tolong, jika Anda atau tim Anda tidak mencari API "termasuk baterai", pertimbangkan untuk mencari sesuatu seperti spesifikasi OpenAPI baru yang dapat membantu membangun fondasi yang lebih kuat hari ini!
Jika Anda menikmati artikel ini (atau jika Anda membencinya) dan ingin memberi saya umpan balik, silakan temukan saya di Twitter sebagai @ebaerbaerbaer atau LinkedIn di ericjbaer.