Log pengembang front-end Habr: pemfaktoran ulang dan refleksi

Log pengembang front-end Habr: pemfaktoran ulang dan refleksi

Saya selalu tertarik dengan bagaimana Habr disusun dari dalam, bagaimana alur kerja disusun, bagaimana komunikasi disusun, standar apa yang digunakan dan bagaimana kode umumnya ditulis di sini. Untungnya saya mendapat kesempatan seperti itu, karena saya baru saja menjadi bagian dari tim habra. Dengan menggunakan contoh refactoring kecil pada versi seluler, saya akan mencoba menjawab pertanyaan: bagaimana rasanya bekerja di sini di depan. Dalam programnya: Node, Vue, Vuex dan SSR dengan saus dari catatan tentang pengalaman pribadi di Habr.

Hal pertama yang perlu Anda ketahui tentang tim pengembangan adalah jumlah kami sedikit. Tidak cukup - ini adalah tiga pemain depan, dua pemain belakang dan pemimpin teknis dari seluruh Habr - Baxley. Tentu saja ada juga seorang penguji, seorang desainer, tiga Vadim, seorang sapu ajaib, seorang spesialis pemasaran dan Bumburum lainnya. Namun hanya ada enam kontributor langsung terhadap sumber-sumber Habr. Hal ini sangat jarang terjadi - sebuah proyek dengan audiens jutaan dolar, yang dari luar tampak seperti perusahaan raksasa, pada kenyataannya lebih terlihat seperti startup yang nyaman dengan struktur organisasi yang paling datar.

Seperti banyak perusahaan IT lainnya, Habr menganut ide Agile, praktik CI, dan itu saja. Namun menurut perasaan saya, Habr sebagai sebuah produk lebih berkembang secara bergelombang daripada terus menerus. Jadi, selama beberapa sprint berturut-turut, kami dengan rajin membuat kode sesuatu, mendesain dan mendesain ulang, memecahkan sesuatu dan memperbaikinya, menyelesaikan tiket dan membuat yang baru, menginjak penggaruk dan menembak diri kami sendiri, untuk akhirnya merilis fitur tersebut ke dalam produksi. Dan kemudian ada jeda tertentu, periode pembangunan kembali, waktu untuk melakukan apa yang ada di kuadran “penting-tidak mendesak”.

Sprint “di luar musim” inilah yang akan dibahas di bawah ini. Kali ini termasuk pemfaktoran ulang Habr versi seluler. Secara umum, perusahaan memiliki harapan yang tinggi terhadapnya, dan di masa depan perusahaan ini harus menggantikan seluruh kebun binatang inkarnasi Habr dan menjadi solusi lintas platform universal. Suatu saat akan ada layout adaptif, PWA, mode offline, kustomisasi pengguna, dan banyak hal menarik lainnya.

Mari kita atur tugasnya

Suatu kali, pada stand-up biasa, salah satu bagian depan berbicara tentang masalah dalam arsitektur komponen komentar versi seluler. Mengingat hal ini, kami mengadakan pertemuan mikro dalam format psikoterapi kelompok. Semua orang bergiliran mengatakan di mana sakitnya, mereka mencatat semuanya di kertas, mereka bersimpati, mereka mengerti, hanya saja tidak ada yang bertepuk tangan. Hasilnya adalah daftar 20 masalah, yang memperjelas bahwa mobile Habr masih memiliki jalan yang panjang dan sulit menuju kesuksesan.

Saya terutama memperhatikan efisiensi penggunaan sumber daya dan apa yang disebut antarmuka yang mulus. Setiap hari, dalam perjalanan pulang-kerja-rumah, saya melihat ponsel lama saya mati-matian mencoba menampilkan 20 berita utama di feed. Itu terlihat seperti ini:

Log pengembang front-end Habr: pemfaktoran ulang dan refleksiAntarmuka Mobile Habr sebelum pemfaktoran ulang

Apa yang terjadi di sini? Singkatnya, server menyajikan halaman HTML kepada semua orang dengan cara yang sama, terlepas dari apakah pengguna sudah login atau tidak. Kemudian JS klien dimuat dan meminta data yang diperlukan lagi, tetapi disesuaikan untuk otorisasi. Artinya, kami sebenarnya melakukan pekerjaan yang sama dua kali. Antarmukanya berkedip-kedip, dan pengguna mengunduh ratusan kilobyte ekstra. Secara detail semuanya tampak semakin menyeramkan.

Log pengembang front-end Habr: pemfaktoran ulang dan refleksiSkema SSR-CSR lama. Otorisasi hanya dimungkinkan pada tahap C3 dan C4, ketika Node JS tidak sibuk membuat HTML dan dapat mem-proxy permintaan ke API.

Arsitektur kami pada waktu itu dijelaskan dengan sangat akurat oleh salah satu pengguna Habr:

Versi selulernya jelek. Aku mengatakannya sebagaimana adanya. Kombinasi yang buruk antara RSK dan CSR.

Kami harus mengakuinya, betapapun menyedihkannya.

Saya menilai pilihan yang ada, membuat tiket di Jira dengan deskripsi pada tingkat “sekarang buruk, lakukan dengan benar” dan menguraikan tugas dalam garis besar:

  • menggunakan kembali data,
  • meminimalkan jumlah penarikan ulang,
  • menghilangkan permintaan duplikat,
  • membuat proses pemuatan lebih jelas.

Mari kita gunakan kembali datanya

Secara teori, rendering sisi server dirancang untuk memecahkan dua masalah: agar tidak mengalami keterbatasan mesin pencari dalam hal Pengindeksan SPA dan meningkatkan metriknya FMP (tentu saja memburuk TTI). Dalam skenario klasik yang akhirnya dirumuskan di Airbnb pada tahun 2013 tahun (masih di Backbone.js), SSR adalah aplikasi JS isomorfik yang sama yang berjalan di lingkungan Node. Server hanya mengirimkan tata letak yang dihasilkan sebagai respons terhadap permintaan. Kemudian rehidrasi terjadi di sisi klien, dan kemudian semuanya berfungsi tanpa memuat ulang halaman. Bagi Habr, seperti halnya banyak sumber daya lain yang berisi konten teks, rendering server adalah elemen penting dalam membangun hubungan persahabatan dengan mesin pencari.

Terlepas dari kenyataan bahwa lebih dari enam tahun telah berlalu sejak munculnya teknologi, dan selama ini banyak air yang benar-benar mengalir di bawah jembatan di dunia front-end, bagi banyak pengembang ide ini masih dirahasiakan. Kami tidak tinggal diam dan meluncurkan aplikasi Vue dengan dukungan SSR ke produksi, kehilangan satu detail kecil: kami tidak mengirimkan status awal ke klien.

Mengapa? Tidak ada jawaban pasti untuk pertanyaan ini. Entah mereka tidak ingin meningkatkan ukuran respons dari server, atau karena banyak masalah arsitektur lainnya, atau memang tidak berhasil. Dengan satu atau lain cara, membuang status dan menggunakan kembali semua yang dilakukan server tampaknya cukup tepat dan berguna. Tugasnya sebenarnya sepele - negara hanya disuntikkan ke dalam konteks eksekusi, dan Vue secara otomatis menambahkannya ke layout yang dihasilkan sebagai variabel global: window.__INITIAL_STATE__.

Salah satu masalah yang muncul adalah ketidakmampuan untuk mengubah struktur siklik menjadi JSON (referensi melingkar); diselesaikan hanya dengan mengganti struktur tersebut dengan struktur datar.

Selain itu, ketika berhadapan dengan konten UGC, Anda harus ingat bahwa data harus dikonversi ke entitas HTML agar tidak merusak HTML. Untuk tujuan ini kami menggunakan he.

Meminimalkan penarikan ulang

Seperti yang Anda lihat dari diagram di atas, dalam kasus kami, satu instance Node JS menjalankan dua fungsi: SSR dan "proxy" di API, tempat otorisasi pengguna terjadi. Keadaan ini membuat otorisasi tidak mungkin dilakukan saat kode JS berjalan di server, karena nodenya berulir tunggal, dan fungsi SSR sinkron. Artinya, server tidak bisa mengirim permintaan ke dirinya sendiri saat tumpukan panggilan sedang sibuk dengan sesuatu. Ternyata kami memperbarui statusnya, tetapi antarmuka tidak berhenti berkedut, karena data pada klien harus diperbarui dengan mempertimbangkan sesi pengguna. Kami perlu mengajari aplikasi kami untuk memasukkan data yang benar ke keadaan awal, dengan mempertimbangkan login pengguna.

Hanya ada dua solusi untuk masalah ini:

  • melampirkan data otorisasi ke permintaan lintas server;
  • membagi lapisan Node JS menjadi dua contoh terpisah.

Solusi pertama memerlukan penggunaan variabel global di server, dan solusi kedua memperpanjang batas waktu penyelesaian tugas setidaknya satu bulan.

Bagaimana cara membuat pilihan? Habr sering kali bergerak di jalur yang hambatannya paling kecil. Secara informal, ada keinginan umum untuk mengurangi siklus dari ide menjadi prototipe seminimal mungkin. Model sikap terhadap produk agak mengingatkan pada postulat booking.com, dengan satu-satunya perbedaan adalah bahwa Habr menanggapi umpan balik pengguna dengan lebih serius dan memercayai Anda, sebagai pengembang, untuk membuat keputusan tersebut.

Mengikuti logika ini dan keinginan saya sendiri untuk menyelesaikan masalah dengan cepat, saya memilih variabel global. Dan, seperti yang sering terjadi, cepat atau lambat Anda harus membayarnya. Kami segera membayar: kami bekerja di akhir pekan, membereskan konsekuensinya, tulis postmortem dan mulai membagi server menjadi dua bagian. Kesalahannya sangat bodoh, dan bug yang melibatkannya tidak mudah untuk direproduksi. Dan ya, sungguh memalukan untuk hal ini, tetapi dengan satu atau lain cara, tersandung dan mengeluh, PoC saya dengan variabel global tetap masuk ke produksi dan bekerja dengan cukup sukses sambil menunggu perpindahan ke arsitektur "dua node" yang baru. Ini adalah langkah penting, karena secara formal tujuannya telah tercapai - SSR belajar untuk menghadirkan halaman yang sepenuhnya siap digunakan, dan UI menjadi lebih tenang.

Log pengembang front-end Habr: pemfaktoran ulang dan refleksiAntarmuka Mobile Habr setelah tahap pertama refactoring

Pada akhirnya, arsitektur SSR-CSR versi seluler mengarah pada gambaran ini:

Log pengembang front-end Habr: pemfaktoran ulang dan refleksiSirkuit SSR-CSR “dua simpul”. Node JS API selalu siap untuk I/O asinkron dan tidak diblokir oleh fungsi SSR, karena fungsi SSR terletak di instance terpisah. Rantai kueri #3 tidak diperlukan.

Menghilangkan permintaan duplikat

Setelah manipulasi dilakukan, rendering awal halaman tidak lagi memicu epilepsi. Namun penggunaan Habr lebih lanjut dalam mode SPA masih menimbulkan kebingungan.

Karena dasar aliran pengguna adalah transisi formulir daftar artikel → artikel → komentar dan sebaliknya, mengoptimalkan konsumsi sumber daya dalam rantai ini adalah hal yang penting.

Log pengembang front-end Habr: pemfaktoran ulang dan refleksiKembali ke feed postingan memicu permintaan data baru

Tidak perlu menggali lebih dalam. Pada screencast di atas Anda dapat melihat bahwa aplikasi meminta ulang daftar artikel saat menggeser ke belakang, dan selama permintaan kami tidak melihat artikel tersebut, yang berarti data sebelumnya hilang entah kemana. Sepertinya komponen daftar artikel menggunakan status lokal dan hilang saat dimusnahkan. Faktanya, aplikasi tersebut menggunakan keadaan global, tetapi arsitektur Vuex dibangun secara langsung: modul diikat ke halaman, yang kemudian diikat ke rute. Selain itu, semua modul bersifat “sekali pakai” - setiap kunjungan berikutnya ke halaman tersebut menulis ulang seluruh modul:

ArticlesList: [
  { Article1 },
  ...
],
PageArticle: { ArticleFull1 },

Secara total, kami memiliki modul Daftar Artikel, yang berisi objek bertipe Artikel dan modul HalamanArtikel, yang merupakan versi objek yang diperluas Artikel, semacam Artikel Penuh. Pada umumnya, implementasi ini tidak membawa sesuatu yang buruk - ini sangat sederhana, bahkan bisa dikatakan naif, tetapi sangat dapat dimengerti. Jika Anda mengatur ulang modul setiap kali Anda mengubah rute, maka Anda bahkan dapat menggunakannya. Namun, berpindah antar feed artikel, misalnya /umpan → /semua, dijamin akan membuang segala sesuatu yang berhubungan dengan feed pribadi, karena kami hanya punya satu Daftar Artikel, di mana Anda perlu memasukkan data baru. Hal ini sekali lagi membawa kita pada duplikasi permintaan.

Setelah mengumpulkan semua yang dapat saya gali tentang topik tersebut, saya merumuskan struktur negara baru dan mempresentasikannya kepada rekan-rekan saya. Diskusinya memakan waktu lama, namun pada akhirnya argumen yang mendukung lebih besar daripada keraguannya, dan saya mulai menerapkannya.

Logika suatu solusi paling baik diungkapkan dalam dua langkah. Pertama kita mencoba memisahkan modul Vuex dari halaman dan mengikat langsung ke rute. Ya, akan ada lebih banyak data di penyimpanan, pengambil akan menjadi sedikit lebih kompleks, tetapi kami tidak akan memuat artikel dua kali. Untuk versi seluler, ini mungkin argumen terkuat. Ini akan terlihat seperti ini:

ArticlesList: {
  ROUTE_FEED: [ 
    { Article1 },
    ...
  ],
  ROUTE_ALL: [ 
    { Article2 },
    ...
  ],
}

Namun bagaimana jika daftar artikel bisa tumpang tindih di antara beberapa rute dan bagaimana jika kita ingin menggunakan kembali data objek Artikel untuk merender halaman posting, mengubahnya menjadi Artikel Penuh? Dalam hal ini, akan lebih logis untuk menggunakan struktur berikut:

ArticlesIds: {
  ROUTE_FEED: [ '1', ... ],
  ROUTE_ALL: [ '1', '2', ... ],
},
ArticlesList: {
  '1': { Article1 }, 
  '2': { Article2 },
  ...
}

Daftar Artikel ini hanya semacam gudang artikel. Semua artikel yang diunduh selama sesi pengguna. Kami memperlakukannya dengan sangat hati-hati, karena ini adalah lalu lintas yang mungkin telah diunduh melalui rasa sakit di suatu tempat di metro antar stasiun, dan kami pasti tidak ingin menimbulkan rasa sakit ini lagi kepada pengguna dengan memaksanya memuat data yang sudah dia miliki. diunduh. Sebuah Objek ArtikelId hanyalah sebuah array ID (seolah-olah “tautan”) ke objek Artikel. Struktur ini memungkinkan Anda menghindari duplikasi data umum pada rute dan penggunaan kembali objek Artikel saat merender halaman posting dengan menggabungkan data tambahan ke dalamnya.

Output dari daftar artikel juga menjadi lebih transparan: komponen iterator melakukan iterasi melalui array dengan ID artikel dan menggambar komponen penggoda artikel, meneruskan Id sebagai prop, dan komponen anak, pada gilirannya, mengambil data yang diperlukan dari Daftar Artikel. Saat Anda membuka halaman publikasi, kami mendapatkan tanggal yang sudah ada Daftar Artikel, kami membuat permintaan untuk mendapatkan data yang hilang dan menambahkannya ke objek yang ada.

Mengapa pendekatan ini lebih baik? Seperti yang saya tulis di atas, pendekatan ini lebih lembut terhadap data yang diunduh dan memungkinkan Anda untuk menggunakannya kembali. Namun selain itu, ini membuka jalan bagi beberapa kemungkinan baru yang sangat cocok dengan arsitektur tersebut. Misalnya, melakukan polling dan memuat artikel ke feed saat muncul. Kita cukup menaruh postingan terbaru di “storage” Daftar Artikel, simpan daftar ID baru yang terpisah ArtikelId dan memberi tahu pengguna tentang hal itu. Ketika kita mengklik tombol “Tampilkan publikasi baru”, kita cukup memasukkan ID baru ke awal susunan daftar artikel saat ini dan semuanya akan bekerja hampir secara ajaib.

Menjadikan pengunduhan lebih menyenangkan

Lapisan gula pada kue refactoring adalah konsep kerangka, yang membuat proses pengunduhan konten di Internet yang lambat menjadi tidak terlalu menjijikkan. Tidak ada diskusi mengenai hal ini; jalur dari ide ke prototipe memakan waktu dua jam. Desainnya praktis dibuat sendiri, dan kami mengajarkan komponen kami untuk merender blok div yang sederhana dan nyaris tidak berkedip sambil menunggu data. Secara subyektif, pendekatan pemuatan ini sebenarnya mengurangi jumlah hormon stres dalam tubuh pengguna. Kerangkanya terlihat seperti ini:

Log pengembang front-end Habr: pemfaktoran ulang dan refleksi
Habraloading

Mencerminkan

Saya sudah bekerja di Habré selama enam bulan dan teman-teman saya masih bertanya: bagaimana Anda suka di sana? Oke, nyaman - ya. Namun ada sesuatu yang membuat karya ini berbeda dari yang lain. Saya bekerja dalam tim yang sama sekali tidak peduli dengan produk mereka, tidak mengetahui atau memahami siapa penggunanya. Tapi di sini semuanya berbeda. Di sini Anda merasa bertanggung jawab atas apa yang Anda lakukan. Dalam proses pengembangan suatu fitur, Anda sebagian menjadi pemiliknya, ikut serta dalam semua pertemuan produk terkait fungsi Anda, memberikan saran, dan membuat keputusan sendiri. Membuat produk yang Anda gunakan setiap hari sendiri sangatlah keren, tetapi menulis kode untuk orang yang mungkin lebih baik dari Anda adalah perasaan yang luar biasa (tidak ada sarkasme).

Setelah semua perubahan ini dirilis, kami menerima tanggapan positif, dan itu sangat, sangat bagus. Ini menginspirasi. Terima kasih! Tulis lebih banyak.

Izinkan saya mengingatkan Anda bahwa setelah variabel global kami memutuskan untuk mengubah arsitektur dan mengalokasikan lapisan proxy ke dalam instance terpisah. Arsitektur “dua node” telah dirilis dalam bentuk pengujian beta publik. Sekarang siapa pun dapat beralih ke sana dan membantu kami menjadikan Habr seluler lebih baik. Itu saja untuk hari ini. Saya akan dengan senang hati menjawab semua pertanyaan Anda di komentar.

Sumber: www.habr.com

Tambah komentar