Apollo: 9 bulan - penerbangan normal

gambar


Halo semuanya, nama saya Semyon Levenson, saya bekerja sebagai pemimpin tim di proyek Stream dari Rambler Group dan saya ingin berbicara tentang pengalaman kami dengan Apollo.


Saya akan menjelaskan apa "Stream" itu. Ini adalah layanan otomatis untuk pengusaha, yang memungkinkan Anda untuk menarik pelanggan dari Internet ke bisnis Anda tanpa terlibat dalam periklanan, dan dengan cepat membuat situs sederhana tanpa menjadi ahli tata letak.


Tangkapan layar menunjukkan salah satu langkah untuk membuat halaman arahan.



Apa awalnya?


Dan pada awalnya ada MVP, banyak Twig, jQuery dan tenggat waktu yang sangat ketat. Tapi kami pergi dengan cara yang tidak standar dan memutuskan untuk membuat desain ulang. Desain ulang tidak dalam arti "gaya ditambal", tetapi memutuskan untuk sepenuhnya meninjau sistem. Dan ini adalah tahap yang baik bagi kami untuk merakit frontend yang sempurna. Bagaimanapun, kami, tim pengembangan, harus terus mendukung ini dan melaksanakan tugas-tugas lain atas dasar ini, untuk mencapai tujuan baru yang ditetapkan oleh tim produk.


Departemen kami telah mengumpulkan cukup banyak keahlian dalam menggunakan React. Saya tidak ingin menghabiskan 2 minggu menyiapkan webpack, jadi saya memutuskan untuk menggunakan CRA (Buat Aplikasi Bereaksi). Untuk style, Styled Components diambil, dan di mana tanpa mengetik - mereka mengambil Flow . Mereka menggunakan Redux untuk Manajemen Negara, tetapi sebagai hasilnya ternyata kami sama sekali tidak membutuhkannya, tetapi lebih pada itu nanti.


Kami menyusun frontend sempurna kami dan menyadari bahwa kami telah melupakan sesuatu. Ternyata, kami lupa tentang backend, atau lebih tepatnya tentang interaksi dengannya. Ketika Anda memikirkan tentang apa yang dapat kita gunakan untuk mengatur interaksi ini, hal pertama yang terlintas dalam pikiran - tentu saja, adalah Istirahat. Tidak, kami tidak pergi untuk beristirahat (tersenyum), tetapi mulai berbicara tentang API yang tenang. Pada prinsipnya, cerita itu akrab, membentang untuk waktu yang lama, tetapi kita juga tahu tentang masalah dengannya. Kami akan membicarakannya.


Masalah pertama adalah dokumentasi. Tenang, tentu saja, tidak mengatakan bagaimana mengatur dokumentasi. Di sini ada opsi untuk menggunakan kesombongan yang sama, tetapi sebenarnya itu adalah pengenalan entitas tambahan dan kerumitan proses.


Masalah kedua adalah bagaimana mengatur dukungan untuk versi API.


Masalah penting ketiga adalah sejumlah besar kueri atau titik akhir khusus yang dapat kami berikan penghargaan. Misalkan kita perlu meminta posting, untuk posting ini - komentar dan lebih banyak penulis dari komentar ini. Dalam Rest klasik, kita harus membuat setidaknya 3 pertanyaan. Ya, kami dapat menghargai titik akhir khusus, dan semua ini dapat dikurangi menjadi 1 permintaan, tetapi ini sudah merupakan komplikasi.



Terima kasih Sashko Stubailo untuk ilustrasinya .


Solusi


Dan saat ini, Facebook membantu kami dengan GraphQL. Apa itu GraphQL? Ini adalah platform, tetapi hari ini kita akan melihat salah satu bagiannya - ini adalah Bahasa Query untuk API Anda, hanya sebuah bahasa, dan yang cukup primitif. Dan itu bekerja sesederhana mungkin - saat kami meminta semacam entitas, kami juga mendapatkannya.


Minta:


{ me { id isAcceptedFreeOffer balance } } 

Jawabannya adalah:


 { "me": { "id": 1, "isAcceptedFreeOffer": false, "balance": 100000 } } 

Tapi GraphQL tidak hanya tentang membaca, tetapi juga tentang mengubah data. Untuk melakukan ini, ada mutasi di GraphQL. Mutasi penting karena kita dapat mendeklarasikan respons yang diinginkan dari backend, dengan perubahan yang berhasil. Namun, ada beberapa nuansa. Misalnya, jika mutasi kami memengaruhi data di luar batas grafik.


Contoh mutasi di mana kami menggunakan penawaran gratis:


 mutation { acceptOffer (_type: FREE) { id isAcceptedFreeOffer } } 

Sebagai tanggapan, kami mendapatkan struktur yang sama dengan yang diminta


 { "acceptOffer": { "id": 1, "isAcceptedFreeOffer": true } } 

Interaksi dengan backend GraphQL dapat dilakukan menggunakan pengambilan biasa.


 fetch('/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: '{me { id balance } }' }) }); 

Apa kelebihan GraphQL?


Plus pertama dan sangat keren yang dapat dihargai ketika Anda mulai bekerja dengannya adalah bahwa bahasa ini sangat diketik dan didokumentasikan sendiri. Dengan mendesain skema GraphQL di server, kita dapat langsung menggambarkan tipe dan atribut langsung dalam kode.



Seperti disebutkan di atas, RESTful memiliki masalah versi. GraphQL menerapkan solusi yang sangat elegan untuk ini - usang.



Misalkan kita punya Film, kita perluas, jadi kita punya sutradara. Dan pada titik tertentu kami hanya menjadikan sutradara sebagai tipe terpisah. Pertanyaannya adalah, apa yang harus dilakukan dengan bidang direktur terakhir? Ada dua jawaban untuk itu: kita menghapus bidang ini, atau menandainya sudah usang, dan secara otomatis menghilang dari dokumentasi.


Kami memutuskan secara mandiri apa yang kami butuhkan.


Kami ingat gambar sebelumnya, di mana semuanya berjalan dengan REST, di sini kami memiliki semuanya digabungkan menjadi satu permintaan dan tidak memerlukan penyesuaian dari pengembangan backend. Begitu mereka semua menggambarkannya, dan kita memelintir, memelintir, menyulap.



Tapi bukan tanpa lalat di salep. Pada prinsipnya, tidak ada begitu banyak kelemahan pada GraphQL di frontend, karena pada awalnya dikembangkan untuk menyelesaikan masalah frontend. Tetapi backend tidak berjalan dengan lancar ... Mereka memiliki masalah seperti N +1. Ambil kueri sebagai contoh:


 { landings(_page: 0, limit: 20) { nodes { id title } totalCount } } 

Permintaan sederhana, kami meminta 20 situs dan jumlah situs yang kami miliki. Dan di backend, ini bisa berubah menjadi 21 permintaan basis data. Masalah ini diketahui, dipecahkan. Untuk Node JS ada paket dataloader dari Facebook. Untuk bahasa lain, Anda dapat menemukan solusi Anda sendiri.


Ada juga masalah sarang yang dalam. Misalnya, kami memiliki album, album ini memiliki lagu, dan melalui lagu kami juga bisa mendapatkan album. Untuk melakukan ini, buat pertanyaan berikut:


 { album(id: 42) { songs { title artists } } } 

 { song(id: 1337) { title album { title } } } 

Dengan demikian, kita mendapatkan kueri rekursif, yang juga secara mendasar menempatkan kita sebagai basis.


 query evil { album(id: 42) { songs { album { songs { album { 

Masalah ini juga diketahui, solusi untuk Node JS adalah batas kedalaman GraphQL, untuk bahasa lain ada juga solusi.


Jadi, kami memutuskan pada GraphQL. Saatnya memilih perpustakaan yang akan berfungsi dengan GraphQL API. Contoh dalam beberapa baris dengan fetch, yang diperlihatkan di atas, hanyalah transport. Namun berkat skema dan deklaratifnya, kami juga dapat men-cache permintaan di depan, dan bekerja dengan kinerja yang lebih baik dengan backend GraphQL.


Jadi kami memiliki dua pemain utama - Relay dan Apollo.


Relay


Relay adalah pengembangan Facebook, mereka menggunakannya sendiri. Seperti Oculus, Circle CI, Arsti dan Friday.


Apa kelebihan Relay?


Nilai tambah langsungnya adalah pengembangnya adalah Facebook. React, Flow, dan GraphQL adalah perkembangan Facebook, yang semuanya adalah teka-teki jigsaw yang dirancang satu sama lain. Di mana kita tanpa bintang di Github, Relay memiliki hampir 11.000, Apollo memiliki 7600 untuk perbandingan. Relay yang keren adalah Relay-compiler, alat yang mengoptimalkan dan menganalisis permintaan GraphQL Anda pada tingkat pembangunan proyek Anda . Kita dapat mengasumsikan bahwa ini hanya untuk GraphQL:


 #  Relay-compiler foo { # type FooType id ... on FooType { # matches the parent type, so this is extraneous id } } #  foo { id } 

Apa kontra dari Relay?


Minus pertama * adalah kurangnya SSR di luar kotak. Github masih memiliki masalah terbuka. Mengapa di bawah tanda bintang - karena sudah ada solusi, tetapi mereka pihak ketiga, dan di samping itu, cukup ambigu.



Sekali lagi, Relay adalah spesifikasi. Faktanya adalah GraphQL sudah menjadi spesifikasi, dan Relay adalah spesifikasi di atas spesifikasi.



Misalnya, Relay pagination diterapkan secara berbeda, kursor muncul di sini.


 { friends(first: 10, after: "opaqueCursor") { edges { cursor node { id name } } pageInfo { hasNextPage } } } 

Kami tidak lagi menggunakan offset dan batas yang biasa. Untuk feed di feed, ini adalah topik yang bagus, tetapi ketika kita mulai melakukan semua jenis grid, maka ada rasa sakit.


Facebook memecahkan masalahnya dengan menulis perpustakaan untuk React. Ada solusi untuk perpustakaan lain, untuk vue.js, misalnya - vue-relay . Tetapi jika kita memperhatikan jumlah bintang dan komit, maka di sini juga, tidak semuanya begitu lancar dan bisa tidak stabil. Sebagai contoh, Buat Bereaksi Aplikasi keluar dari kotak CRA mencegah Anda menggunakan Relay-compiler. Tetapi Anda dapat mengatasi batasan ini dengan reaksi-aplikasi-rewired .



Apollo


Kandidat kedua kami adalah Apollo . Dikembangkan oleh timnya Meteor . Apollo menggunakan perintah terkenal seperti: AirBnB, ticketmaster, Opentable, dll.


Apa kelebihan Apollo?


Nilai tambah penting pertama adalah bahwa Apollo dikembangkan sebagai kerangka kerja agnostik perpustakaan. Misalnya, jika kita sekarang ingin menulis ulang semua yang ada di Angular, maka ini tidak akan menjadi masalah, Apollo bekerja dengan ini. Dan Anda bahkan dapat menulis semuanya di Vanilla.


Apollo memiliki dokumentasi yang keren, ada solusi siap pakai untuk masalah umum.



Plus lainnya Apollo - API yang kuat. Pada prinsipnya, mereka yang bekerja dengan Redux akan menemukan pendekatan umum di sini: ada ApolloProvider (seperti Provux untuk Redux), dan alih-alih menyimpan untuk Apollo ini disebut klien:


 import { ApolloProvider } from 'react-apollo'; import { ApolloClient } from './ApolloClient'; const App = () => ( <ApolloProvider client={ApolloClient}> ... </ApolloProvider> ); 

Pada tingkat komponen itu sendiri, kami memiliki graphql HOC yang disediakan sebagai terhubung. Dan kami menulis permintaan GraphQL yang sudah ada di dalam, seperti MapStateToProps di Redux.


 import { graphql } from 'react-apollo'; import gql from 'graphql-tag'; import { Landing } from './Landing'; graphql(gql` { landing(id: 1) { id title } } `)(Landing); 

Tetapi ketika kita melakukan MapStateToProps di Redux, kita mengambil data lokal. Jika tidak ada data lokal, maka Apollo sendiri pergi ke server untuk mereka. Alat peraga yang sangat nyaman jatuh ke dalam komponen itu sendiri.


 function Landing({ data, loading, error, refetch, ...other }) { ... } 

Ini adalah:
• data;
• status unduhan;
• kesalahan jika terjadi;
fungsi pembantu seperti mengambil ulang untuk memuat kembali data atau mengambil lebih ke pagination. Ada juga nilai tambah yang besar untuk Apollo dan Relay, yang merupakan UI Optimis. Ini memungkinkan Anda untuk melakukan undo / redo di tingkat permintaan:


 this.props.setNotificationStatusMutation({ variables: { … }, optimisticResponse: { … } }); 

Misalnya, pengguna mengklik tombol "suka", dan "suka" segera dihitung. Dalam hal ini, permintaan ke server akan dikirim di latar belakang. Jika beberapa kesalahan terjadi selama proses pengiriman, maka data yang dapat diubah akan kembali ke kondisi aslinya sendiri.


Render sisi server diimplementasikan dengan baik, kami menetapkan satu flag pada klien dan semuanya siap.


 new ApolloClient({ ssrMode: true, ... }); 

Tetapi di sini saya ingin berbicara tentang Initial State. Ketika Apollo memasaknya untuk dirinya sendiri, semuanya bekerja dengan baik.


 <script> window.__APOLLO_STATE__ = client.extract(); </script> const client = new ApolloClient({ cache: new InMemoryCache().restore(window.__APOLLO_STATE__), link }); 

Tetapi kami tidak memiliki rendering sisi Server, dan backend mendorong kueri GraphQL tertentu ke variabel global. Di sini Anda memerlukan kruk kecil, Anda perlu menulis fungsi Transform yang respons GraphQL dari backend akan berubah menjadi format yang diperlukan untuk Apollo.


 <script> window.__APOLLO_STATE__ = transform({…}); </script> const client = new ApolloClient({ cache: new InMemoryCache().restore(window.__APOLLO_STATE__), link }); 

Kelebihan lain dari Apollo adalah dapat disesuaikan dengan baik. Kita semua ingat middleware dari Redux, semuanya sama di sini, hanya ini yang disebut tautan.



Saya ingin mencatat dua tautan secara terpisah: apollo-link-state , yang diperlukan untuk menyimpan keadaan lokal tanpa adanya Redux, dan apollo-link-rest , jika kita ingin menulis permintaan GraphQL ke API Istirahat. Namun, dengan yang terakhir Anda harus sangat berhati-hati, karena masalah tertentu mungkin timbul.


Apollo juga memiliki kontra


Mari kita lihat sebuah contoh. Ada masalah kinerja yang tidak terduga: 2.000 item diminta di frontend (itu adalah direktori), dan masalah kinerja dimulai. Setelah melihatnya di debugger, ternyata Apollo memakan banyak sumber daya saat membaca, masalah ini pada dasarnya ditutup, sekarang semuanya baik-baik saja, tetapi ada dosa seperti itu.


Juga, pembuatan ulang ternyata sangat tidak jelas ...


 function Landing({ loading, refetch, ...other }) { ... } 

Tampaknya ketika kami melakukan permintaan ulang data, terlebih lagi, jika permintaan sebelumnya berakhir dengan kesalahan, maka pemuatan akan menjadi benar. Tapi tidak!


Agar ini terjadi, Anda perlu menentukan notifyOnNetworkStatusChange: true di graphql HOC, atau menyimpan keadaan pengambilan kembali secara lokal.


Apollo vs Relay


Jadi, kami mendapat meja seperti itu, kami semua menimbang, menghitung, dan kami memiliki 76% di belakang Apollo.



Jadi kami memilih perpustakaan dan mulai bekerja.


Tetapi saya ingin mengatakan lebih banyak tentang toolchain.


Semuanya sangat baik di sini, ada berbagai tambahan untuk editor, di tempat yang lebih baik, di tempat yang lebih buruk. Ada juga apollo-codegen, yang menghasilkan file-file berguna, misalnya tipe-aliran, dan pada dasarnya menarik skema dari GraphQL API.


Judulnya "Gila tangan" atau apa yang kami lakukan di rumah


Hal pertama yang kami temui adalah bahwa pada dasarnya kami perlu meminta data.


 graphql(BalanceQuery)(BalanceItem); 

Kami memiliki kondisi umum: memuat, penanganan kesalahan. Kami menulis elang kami sendiri (asyncCard), yang terhubung melalui komposisi graqhql dan asyncCard.


 compose( graphql(BalanceQuery), AsyncCard )(BalanceItem); 

Saya juga ingin berbicara tentang fragmen. Ada komponen LandingItem dan ia tahu data apa yang dibutuhkannya dari GraphQL API. Kami menetapkan properti fragmen, tempat kami menentukan bidang dari entitas Landing.


 const LandingItem = ({ content }: Props) => ( <LandingItemStyle></LandingItemStyle> ); LandingItem.fragment = gql` fragment LandingItem on Landing { ... } `; 

Sekarang, pada tingkat penggunaan komponen, kami menggunakan fragmennya dalam permintaan akhir.


 query LandingsDashboard { landings(...) { nodes { ...LandingItem } totalCount } ${LandingItem.Fragment} } 

Dan katakanlah sebuah tugas terbang untuk menambahkan status ke halaman arahan ini - bukan masalah. Kami menambahkan properti ke render dan fragmen. Dan semuanya sudah siap. Prinsip tanggung jawab tunggal dalam semua kemuliaan.


 const LandingItem = ({ content }: Props) => ( <LandingItemStyle><LandingItemStatus … /> </LandingItemStyle> ); LandingItem.fragment = gql` fragment LandingItem on Landing { ... status } `; 

Masalah apa lagi yang kita miliki?


Kami memiliki sejumlah widget di situs kami yang mengajukan permintaan individual.



Selama pengujian, ternyata semua ini melambat. Kami memiliki pemeriksaan keamanan yang sangat panjang, dan setiap permintaan sangat mahal. Ini juga ternyata tidak ada masalah, ada Apollo-link-batch-http


 new BatchHttpLink({ batchMax: 10, batchInterval: 10 }); 

Ini dikonfigurasikan sebagai berikut: kami mengirimkan sejumlah permintaan yang dapat kami gabungkan dan berapa lama tautan ini akan menunggu setelah permintaan pertama muncul.
Dan ternyata seperti ini: pada saat yang sama semuanya memuat, dan pada saat yang sama semuanya datang. Perlu dicatat bahwa jika selama ini menggabungkan salah satu subqueries kembali dengan kesalahan, maka kesalahan hanya akan bersamanya, dan tidak dengan seluruh permintaan.


Saya ingin mengatakan secara terpisah bahwa musim gugur yang lalu ada pembaruan dari Apollo pertama ke yang kedua


Pada awalnya adalah Apollo dan Redux


 'react-apollo' 'redux' 

Kemudian Apollo menjadi lebih modular dan diperluas, modul-modul ini dapat dikembangkan secara mandiri. Memori apollo-cache-sama.


 'react-apollo' 'apollo-client' 'apollo-link-batch-http' 'apollo-cache-inmemory' 'graphql-tag' 

Perlu dicatat bahwa Redux tidak, dan ternyata, pada prinsipnya, tidak diperlukan.


Kesimpulan:


  1. Waktu pengiriman fitur menurun, kami tidak membuang waktu untuk menggambarkan tindakan, mengurangi di Redux, dan kurang menyentuh backend
  2. Antifragilitas muncul karena Analisis statis API memungkinkan Anda untuk menghapus masalah ketika frontend mengharapkan satu hal dan backend mengembalikan yang sama sekali berbeda.
  3. Jika Anda mulai bekerja dengan GraphQL - coba Apollo, jangan kecewa.

PS Anda juga dapat menonton video dari presentasi saya di Rambler Front & Meet up # 4


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


All Articles