
Saya selalu tertarik pada bagaimana Habr diatur dari dalam, bagaimana alur kerja dibangun, bagaimana komunikasi dibangun, standar apa yang diterapkan dan bagaimana kode ditulis di sini. Untungnya, kesempatan seperti itu muncul pada saya, karena baru-baru ini saya menjadi bagian dari habracommand. Menggunakan contoh refactoring kecil dari versi seluler, saya akan mencoba menjawab pertanyaan: bagaimana rasanya bekerja di sini di bagian depan. Dalam program ini: Node, Vue, Vuex dan SSR dengan saus dari catatan tentang pengalaman pribadi di Habré.
Hal pertama yang perlu Anda ketahui tentang tim pengembangan adalah kami sedikit. Sedikit tiga front, dua backs dan teknis dari semua Habr - Bucksley. Tentu saja, ada juga tester, perancang, tiga Vadim, sapu ajaib, seorang pemasar dan Bumburum lainnya. Tetapi hanya ada enam kontributor langsung untuk jenis Habra. Ini sangat jarang - sebuah proyek dengan audiensi jutaan dolar yang terlihat seperti perusahaan raksasa dari luar sebenarnya lebih mirip startup yang nyaman dengan struktur organisasi paling datar.
Seperti banyak perusahaan IT lainnya, Habr menyatakan ide-ide Agile, praktik CI, dan itu saja. Tetapi menurut perasaan saya, Habr sebagai sebuah produk berkembang lebih besar daripada terus menerus. Jadi untuk beberapa sprint berturut-turut, kami bekerja keras dalam mengkodekan, mendesain, dan mendesain ulang, memecahkan sesuatu dan memperbaiki, menyelesaikan tiket dan memulai yang baru, menginjak penggaruk dan menembak diri kami di kaki untuk akhirnya merilis fitur dalam prod. Dan kemudian muncul jeda, masa pembangunan kembali, waktu untuk melakukan apa yang ada di kuadran "penting-tidak-mendesak".
Tentang sprint "off-season" akan dibahas di bawah ini. Kali ini mendapat refactoring versi mobile Habr. Secara umum, perusahaan memiliki harapan tinggi untuk itu, dan di masa depan ia harus menggantikan seluruh kebun binatang inkarnasi Habr dan menjadi solusi lintas platform universal. Suatu hari akan muncul tata letak adaptif, dan PWA, dan mode offline, dan kustomisasi pengguna, dan banyak hal menarik.
Kami mengatur tugas
Suatu ketika, di stand-up biasa, salah satu front berbicara tentang masalah dalam arsitektur komponen komentar versi mobile. Dari presentasi ini, kami mengadakan pertemuan mikro dalam format psikoterapi kelompok. Masing-masing mengatakan di mana dia merasakan sakit, semuanya tertata di atas kertas, bersimpati, mengerti, kecuali bahwa tidak ada yang bertepuk tangan. Hasilnya adalah daftar 20 masalah, yang membuatnya jelas bahwa Habr ponsel harus menempuh jalan panjang dan sulit menuju kesuksesan.
Perhatian utama saya adalah efisiensi sumber daya dan apa yang disebut antarmuka yang halus. Setiap hari di rute "rumah-kerja-rumah", saya melihat telepon lama saya berusaha menampilkan 20 judul di sungai. Itu terlihat seperti ini:
Antarmuka Habr Mobile sebelum refactoringApa yang sedang terjadi di sini? Singkatnya, server memberikan halaman HTML kepada semua orang dengan cara yang sama, terlepas dari apakah pengguna masuk atau tidak. Kemudian JS klien dimuat dan sekali lagi meminta data yang diperlukan, tetapi dengan amandemen untuk otorisasi. Faktanya, kami melakukan pekerjaan yang sama dua kali. Antarmukanya berkedip, dan pengguna mengunduh seratus kilobyte yang bagus. Secara detail, semuanya tampak lebih menyeramkan.
Sirkuit SSR-CSR lama. Otorisasi hanya dimungkinkan pada tahap C3 dan C4, ketika Node JS tidak sibuk menghasilkan HTML dan dapat mem-proxy permintaan API.Arsitektur kami saat itu sangat akurat dijelaskan oleh salah satu pengguna Habr:
Versi selulernya sial. Saya berbicara apa adanya. Kombinasi yang buruk dari RSK bersama dengan CSR.
Kami harus mengakuinya, betapapun menyedihkannya itu.
Saya menemukan opsi, menetapkan diri saya tiket di "Jira" dengan deskripsi di tingkat "Sekarang itu buruk, buat peraturan" dan dengan pukulan lebar saya menguraikan tugas:
- menggunakan kembali data
- meminimalkan jumlah redraws,
- kecualikan permintaan duplikat
- buat proses memuat lebih jelas.
Gunakan kembali data
Secara teori, rendering sisi server dirancang untuk menyelesaikan dua masalah: tidak menderita keterbatasan mesin pencari terkait
pengindeksan SPA dan untuk meningkatkan metrik
FMP (
TTI yang semakin buruk). Dalam skenario klasik, yang akhirnya
dirumuskan di Airbnb pada tahun 2013 (kembali di Backbone.js), SSR adalah aplikasi JS isomorfik yang sama yang berjalan di lingkungan Node. Server hanya mengembalikan tata letak yang dihasilkan sebagai respons terhadap permintaan. Kemudian rehidrasi terjadi di sisi klien, dan kemudian semuanya berfungsi tanpa memuat ulang laman. Untuk Habr, dan juga untuk banyak sumber daya lain yang berisi teks, rendering server adalah elemen penting dalam membangun hubungan yang bersahabat dengan mesin pencari.
Terlepas dari kenyataan bahwa lebih dari enam tahun telah berlalu sejak munculnya teknologi, dan selama ini, benar-benar banyak air telah mengalir di dunia frontend, bagi banyak pengembang ide ini masih tertutup oleh selubung kerahasiaan. Kami tidak berdiri di samping, dan meluncurkan aplikasi Vue dengan dukungan SSR ke prod, kehilangan satu detail kecil: kami tidak melemparkan keadaan awal ke klien.
Mengapa Tidak ada jawaban pasti untuk pertanyaan ini. Entah mereka tidak ingin meningkatkan ukuran respon dari server, atau karena banyak masalah arsitektur lainnya, atau hanya tidak lepas landas. Dengan satu atau lain cara, melemparkan keadaan dan menggunakan kembali segala sesuatu yang dilakukan server tampaknya cukup tepat dan berguna. Tugas ini sebenarnya sepele -
negara hanya menyuntikkan dirinya ke dalam konteks eksekusi, dan Vue secara otomatis menambahkannya ke tata letak yang dihasilkan sebagai variabel global:
window.__INITIAL_STATE__
.
Salah satu masalah yang muncul adalah ketidakmampuan untuk mengubah struktur
lingkaran menjadi JSON; diselesaikan dengan hanya mengganti struktur tersebut dengan analog datar mereka.
Selain itu, ketika berurusan dengan konten UGC, ingatlah bahwa data harus dikonversi ke entitas HTML agar tidak merusak HTML. Untuk tujuan ini kami menggunakan
dia .
Minimalkan redraws
Seperti yang dapat dilihat dari diagram di atas, dalam kasus kami, satu instance Node JS melakukan dua fungsi: SSR dan "proxy" di API, di mana pengguna diotorisasi. Keadaan ini membuat otorisasi tidak mungkin pada saat eksekusi kode JS di server, karena node adalah single-threaded dan fungsi SSR sinkron. Artinya, server tidak bisa mengirim permintaan ke dirinya sendiri sementara tumpukan panggilan sibuk dengan sesuatu. Ternyata kami melewatkan negara, tetapi antarmuka tidak berhenti berkedut, karena data pada klien harus diperbarui dengan mempertimbangkan sesi pengguna. Itu perlu untuk mengajar aplikasi kita untuk memasukkan data yang benar dalam keadaan awal, dengan mempertimbangkan login pengguna.
Hanya ada dua solusi untuk masalah ini:
- untuk melekatkan data otorisasi ke permintaan interserver;
- Membagi layer Node JS menjadi dua instance terpisah.
Solusi pertama membutuhkan penggunaan variabel global di server, dan yang kedua memperpanjang waktu yang dibutuhkan untuk menyelesaikan tugas setidaknya satu bulan.
Bagaimana cara membuat pilihan? Habr sering bergerak di sepanjang jalan yang paling tidak resistan. Secara informal, ada keinginan umum tertentu untuk meminimalkan siklus dari ide ke prototipe. Model sikap terhadap produk agak mengingatkan pada postulat booking.com, dengan satu-satunya perbedaan adalah bahwa Habr jauh lebih serius tentang umpan balik pengguna dan memercayai adopsi keputusan seperti itu kepada Anda sebagai pengembang.
Mengikuti logika ini dan keinginan saya sendiri untuk menyelesaikan masalah dengan cepat, saya memilih variabel global. Dan, seperti ini sering terjadi, cepat atau lambat mereka harus membayarnya. Kami segera membayar: kami bekerja pada akhir pekan, mengambil konsekuensinya, menulis
post mortem dan mulai membagi server menjadi dua bagian. Kesalahannya sangat bodoh, dan bug dengan partisipasinya tidak mudah untuk direproduksi. Dan ya, untuk memalukan, tapi entah bagaimana, tersandung dan mendengus, PoC saya dengan variabel global masih masuk ke produksi dan cukup berhasil bekerja dalam mengantisipasi pindah ke arsitektur "dua hari" yang baru. Ini adalah langkah penting, karena secara formal tujuan tercapai - SSR belajar untuk memberikan halaman yang benar-benar siap untuk digunakan, dan UI menjadi jauh lebih tenang.
Antarmuka Habr Mobile setelah tahap pertama refactoringPada akhirnya, arsitektur SSR-CSR versi seluler mengarah ke gambar ini:

Skema SSR-CSR "dua hari". Node JS API selalu siap untuk asynchronous I / O dan tidak diblokir oleh fungsi SSR, karena yang terakhir adalah dalam contoh terpisah. Rantai kueri # 3 tidak diperlukan.Kecualikan permintaan duplikat
Setelah manipulasi, rendering halaman awal berhenti memprovokasi epilepsi. Tetapi penggunaan lebih lanjut dari Habr dalam mode SPA masih menyebabkan kebingungan.
Karena aliran pengguna didasarkan pada transisi dari
daftar bentuk
artikel → artikel → komentar dan sebaliknya, penting untuk mengoptimalkan konsumsi sumber daya rantai ini di tempat pertama.
Pengembalian ke umpan posting memicu permintaan data baruSaya tidak perlu menggali lebih dalam. Pada screencast di atas, dapat dilihat bahwa aplikasi meminta kembali daftar artikel ketika menggesek kembali, dan selama permintaan kita tidak melihat artikel, sehingga data sebelumnya menghilang di suatu tempat. Sepertinya komponen daftar artikel menggunakan negara bagian dan kehilangannya saat dihancurkan. Sebenarnya, aplikasi menggunakan negara global, tetapi arsitektur Vuex dibangun di dahi: modul diikat ke halaman, yang pada gilirannya terkait dengan rute. Selain itu, semua modul adalah "satu kali" - setiap kunjungan berikutnya ke halaman menulis ulang seluruh modul:
ArticlesList: [ { Article1 }, ... ], PageArticle: { ArticleFull1 },
Secara total, kami memiliki modul
ArticleList , yang berisi objek dari tipe
artikel dan modul
PageArticle , yang merupakan versi diperpanjang dari objek
Article , semacam
ArticleFull . Secara umum, implementasi ini tidak membawa sesuatu yang mengerikan dalam dirinya sendiri - sangat sederhana, orang bahkan dapat mengatakan secara naif, tetapi sangat jelas. Jika Anda memotong zeroing modul dengan setiap perubahan rute, maka Anda bahkan dapat hidup dengan itu. Namun, transisi di antara umpan artikel, misalnya
/ feed → / semua , dijamin akan membuang segala sesuatu yang terkait dengan umpan pribadi, karena kami hanya memiliki satu
ArticleList untuk memasukkan data baru. Ini lagi mengarah ke permintaan duplikat.
Menyusun segala sesuatu yang berhasil saya gali pada topik, saya merumuskan struktur negara baru dan menyajikannya kepada rekan-rekan saya. Diskusi itu panjang, tetapi pada akhirnya, argumen "untuk" melebihi keraguan, dan saya mulai implementasi.
Logika solusi terbaik diungkapkan dalam dua tahap. Pertama, kami mencoba membuka ikatan modul Vuex dari halaman dan mengikat langsung ke rute. Ya, akan ada sedikit lebih banyak data di toko, getter akan menjadi sedikit lebih rumit, tetapi kami tidak akan memuat artikel dua kali. Untuk versi seluler, ini mungkin argumen yang paling kuat. Akan terlihat seperti ini:
ArticlesList: { ROUTE_FEED: [ { Article1 }, ... ], ROUTE_ALL: [ { Article2 }, ... ], }
Tetapi bagaimana jika daftar artikel dapat tumpang tindih antara beberapa rute, dan bagaimana jika kita ingin menggunakan kembali data objek
Artikel untuk membuat halaman posting, mengubahnya menjadi
ArticleFull ? Dalam hal ini, akan lebih logis untuk menggunakan struktur seperti itu:
ArticlesIds: { ROUTE_FEED: [ '1', ... ], ROUTE_ALL: [ '1', '2', ... ], }, ArticlesList: { '1': { Article1 }, '2': { Article2 }, ... }
ArtikelDaftar di sini hanya semacam repositori artikel. Semua artikel yang diunggah selama sesi pengguna. Kami memperlakukan mereka dengan hati-hati, karena ini adalah lalu lintas yang mungkin telah dimuat melalui rasa sakit di suatu tempat di kereta bawah tanah antara stasiun, dan kami pasti tidak ingin menyebabkan rasa sakit pengguna ini lagi, memaksanya untuk memuat data yang telah ia unduh. Objek ArticleIds hanyalah array pengidentifikasi (seperti "tautan") ke objek
Artikel . Struktur ini memungkinkan Anda untuk tidak menduplikasi data yang umum untuk rute dan menggunakan kembali objek
artikel saat merender halaman posting dengan menggabungkan data yang diperluas ke dalamnya.
Output dari daftar artikel juga menjadi lebih transparan: komponen iterator iterates atas array dengan ID artikel dan menggambar komponen artikel penggoda, melewati Id sebagai alat peraga, dan komponen anak pada gilirannya mengambil data yang diperlukan dari
ArticleList . Ketika Anda pergi ke halaman publikasi, kami mendapatkan tanggal yang ada dari
ArtikelList , membuat permintaan untuk data yang hilang, dan hanya menambahkannya ke objek yang ada.
Mengapa pendekatan ini lebih baik? Seperti yang saya tulis di atas, pendekatan ini lebih berhati-hati dalam kaitannya dengan data yang diunduh dan memungkinkan Anda untuk menggunakannya kembali. Tapi selain itu, ini membuka jalan ke beberapa peluang baru yang sangat cocok dengan arsitektur seperti itu. Misalnya, polling dan mengunggah artikel ke feed saat muncul. Kami hanya dapat menambahkan posting baru ke “store” ArticleList, menyimpan daftar ID baru yang terpisah di
ArticleIds dan memberi tahu pengguna tentang hal ini. Ketika Anda mengklik tombol "Tampilkan publikasi baru", kami cukup memasukkan Id baru di awal array daftar artikel saat ini dan semuanya akan bekerja hampir secara ajaib.
Menjadikan unduhan lebih menyenangkan
Ceri pada kue refactoring adalah konsep kerangka, yang membuat proses mengunduh konten di Internet lambat sedikit kurang menjijikkan. Tidak ada diskusi tentang hal ini, perjalanan dari ide ke prototipe memakan waktu dua jam. Desainnya hampir dibuat sendiri, dan kami mengajarkan komponen kami cara membuat blok div yang sederhana, tidak berkedip-kedip sambil menunggu data. Secara subyektif, pendekatan pemuatan ini benar-benar mengurangi jumlah hormon stres dalam tubuh pengguna. Kerangka terlihat seperti ini:
HabraloadingRenungkan
Saya telah bekerja di Habré selama enam bulan dan teman-teman masih bertanya: baik, bagaimana Anda menyukainya? Bagus, nyaman - ya. Tetapi ada sesuatu yang membedakan pekerjaan ini dari yang lain. Saya bekerja di tim yang sama sekali tidak peduli dengan produk mereka, tidak tahu dan tidak mengerti siapa pengguna mereka. Tapi di sini semuanya berbeda. Di sini Anda merasa bertanggung jawab atas apa yang Anda lakukan. Dalam proses mengembangkan fitur, Anda sebagian menjadi pemiliknya, ikut serta dalam semua rapat produk yang terkait dengan fungsi Anda, membuat saran, dan membuat keputusan sendiri. Membuat produk yang Anda gunakan sendiri setiap hari sangat keren, dan menulis kode untuk orang yang mungkin lebih baik hanya perasaan yang luar biasa (tidak ada sarkasme).
Setelah rilis semua perubahan ini, kami menerima umpan balik positif, dan itu sangat, sangat bagus. Menginspirasi. Terima kasih Menulis lebih banyak
Biarkan saya mengingatkan Anda bahwa setelah variabel global, kami memutuskan untuk mengubah arsitektur dan memisahkan lapisan proksi menjadi contoh terpisah. Arsitektur "dua hari" telah mencapai rilis 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.