Kolaborasi masa nyata kelihatan mudah sehinggalah anda melihat apa yang berlaku di sebalik tabir. Seorang orang menaip. Seorang lagi memadamkan baris tiga perenggan di atas. Seorang lagi menampal keratan kod dari Stack Overflow. Entah bagaimana, dokumen tersebut akhirnya menjadi satu keadaan yang tunggal dan koheren. Membina kelancaran tersebut dari awal, tanpa pengalaman terdahulu dalam WebSockets atau distributed state, kedengaran berisiko. Namun, ia juga kedengaran seperti cara yang betul untuk benar-benar belajar.

Projek ini bermula dari sifar. Tiada kod templat (boilerplate) pinjaman. Tiada panduan YouTube yang sudah siap di mana bahagian yang sukar diringkaskan dalam montaj tiga puluh saat. Matlamatnya adalah untuk membina editor kod kolaboratif di mana pelbagai pengguna boleh menyunting fail yang sama secara serentak, melihat perubahan satu sama lain—dan kursor satu sama lain—sebaik sahaja ia berlaku. Untuk sampai ke tahap itu, kita perlu memahami lapisan pengangkutan (transport layers), model konsistensi, dan masalah rumit dalam menggabungkan suntingan serentak tanpa merosakkan dokumen.

Apa Maksud Sebenar “Masa Nyata”

Kebanyakan aplikasi web selesa dengan kitaran permintaan-respons (request-response cycles). Anda menghantar borang, pelayan menyimpannya, dan anda menyegarkan halaman. Kolaborasi masa nyata memecahkan kontrak tersebut sepenuhnya. Setiap tekanan kekunci adalah satu peristiwa yang mesti tersebar ke setiap klien yang disambungkan yang lain, biasanya dalam milisaat, dan tiba dalam urutan yang mengekalkan makna.

WebSockets adalah pilihan pengangkutan yang jelas di sini kerana ia mengekalkan sambungan dwiarah (full-duplex) yang berterusan antara klien dan pelayan. Tidak seperti HTTP polling, yang membazirkan jalur lebar dengan bertanya “ada apa-apa yang baharu?” setiap beberapa saat, WebSocket kekal terbuka. Apabila pengguna A menaip tanda koma bertitik, aksara tersebut menjadi mesej yang bergerak melalui soket ke pelayan pusat, kemudian disebarkan kepada pengguna B dan C. Bahagian itu agak mudah.

Bahagian yang sukar adalah apa yang berlaku apabila B dan C menaip pada saat yang sama tepat. Jika kedua-dua perubahan sampai ke pelayan hampir serentak, yang mana satu akan menang? Jika anda hanya menyiarkan mesej mengikut urutan ketibaan, anda berisiko kehilangan aksara atau teks yang bercelaru. Strategi last-write-wins yang naif akan gagal kerana ia mengabaikan niat pengguna. Jika saya menaip “hello” di permulaan baris pertama manakala anda menaip “world” di permulaan baris pertama, hasilnya tidak sepatutnya menjadi perlanggaran di mana salah seorang daripada kita terpadam. Ia sepatutnya menjadi “helloworld” atau “worldhello,” yang dipilih secara deterministik. Mencapai perkara itu memerlukan strategi penyelarasan yang memahami struktur dokumen.

Mengapa Bermula Dari Sifar Itu Penting

Terdapat rangka kerja (framework) hebat yang menyembunyikan kerumitan ini. Yjs, Automerge, dan Socket.IO boleh meringkaskan kesukaran tersebut dan menghasilkan prototaip yang berfungsi dalam satu petang. Tetapi menggunakannya tanpa memahami primitif di bawahnya adalah seperti menerbangkan kapal terbang menggunakan autopilot tanpa tahu cara membaca instrumen. Apabila gangguan (turbulence) melanda—dan dalam sistem teragih, ia pasti akan melanda—anda perlu tahu sama ada masalah itu terletak pada lapisan rangkaian, penyelesaian konflik, atau model data anda.

Komitmen di sini adalah untuk mempelajari konsep-konsep tersebut sebelum bergantung kepada perpustakaan (libraries). Ini bermakna menaakul secara manual tentang apa yang berlaku apabila:

  • Seorang klien terputus sambungan di tengah-tengah pengetikan dan menyambung semula sepuluh saat kemudian
  • Dua pengguna memasukkan teks pada kedudukan kursor yang sama secara serentak
  • Seorang pengguna memadam satu blok yang sedang diedit secara aktif oleh pengguna lain
  • Pelayan tergendala dan nod baharu perlu membina semula keadaan dokumen dari awal

Operational Transformation (OT) dan Conflict-free Replicated Data Types (CRDTs) adalah dua keluarga penyelesaian utama bagi masalah ini. Google Docs terkenal kerana membina seni bina awalnya berasaskan OT, yang memerlukan pelayan pusat untuk mengubah operasi antara satu sama lain sebelum melaksanakannya. Sebaliknya, CRDTs direka supaya kemas kini serentak boleh digabungkan secara tempatan tanpa penyelarasan, menjadikannya menarik untuk tetapan peer-to-peer atau berasaskan edge. Memilih antara keduanya—atau pendekatan hibrid—memerlukan pemahaman tentang imbangan (trade-offs) dalam penggunaan memori, jaminan penumpuan (convergence), dan kerumitan pelaksanaan. Membaca tentang imbangan tersebut tidak mencukupi; rancangannya adalah untuk melaksanakan kedua-dua versi naif dan versi yang diperhalusi untuk melihat di mana ia gagal.

Pembinaan Semula, Kesilapan, dan Jalan Buntu

Jangkaan ditetapkan secara jujur. Akan ada tempoh di mana tiada apa yang berfungsi. Percubaan pertama mungkin menggunakan tampalan JSON yang ringkas untuk mewakili perubahan teks, hanya untuk mendapati bahawa JSON tidak mempunyai konsep “indeks 5 dalam perenggan,” jadi dua penyisipan serentak pada indeks yang sama akan menindih satu sama lain dan bukannya bergabung. Percubaan kedua mungkin membina log sejarah linear tersuai, hanya untuk menyedari bahawa memainkan semula log tersebut adalah mimpi ngeri Big O apabila dokumen semakin besar. Percubaan ketiga mungkin berjaya menjalankan WebSockets secara tempatan, kemudian gagal apabila digunakan pada rangkaian sebenar di mana kehilangan paket dan kependaman yang berubah-ubah mengubah segala peraturan.

Geseran itulah tujuannya. Menyalin repositori yang sudah berfungsi akan menyebabkan anda melangkau penyiasatan tentang mengapa barisan (queue) dikosongkan dalam urutan tertentu, atau mengapa pelayan mengekalkan vektor versi (version vector). Membina semula komponen yang sama sebanyak tiga kali adalah lambat, tetapi ia memaksa pemahaman tentang sempadan antara apa yang dilakukan oleh rangka kerja (framework) dan apa yang mesti dikendalikan oleh logik anda sendiri.

Dokumentasi proses ini bukanlah sebuah montaj kejayaan. Ia akan merangkumi kesilapan jalan. Sebagai contoh, membina kesedaran kehadiran (presence awareness)—mengetahui siapa yang dalam talian dan di mana kursor mereka berada—kelihatan seperti ciri kosmetik sehinggalah anda menyedari bahawa ia bergantung pada model ketekalan (consistency model) yang sama dengan teks itu sendiri. Jika pengguna A melihat kursor pengguna B pada lajur 10, kemudian pengguna B menyisipkan empat aksara, ke manakah kursor itu beralih? Tanpa pemahaman bersama tentang topologi dokumen, data kehadiran akan terpesong daripada realiti. Menyelesaikan perkara itu memerlukan penyepaduan kedudukan kursor dengan identiti struktur data asas, bukan sekadar indeks numeriknya. Inilah jenis perincian yang sering diabaikan dalam tutorial kerana ia membosankan, bukan kerana ia tidak penting.

Apa Yang Seterusnya

Pelan hala tuju segera adalah ringkas secara sengaja. Pencapaian pertama adalah:

  • Sebuah pelayan WebSocket mentah yang mengulang (echo) acara aksara, untuk merasai kependaman dan kitaran hayat sambungan secara langsung
  • Buffer rentetan (string buffer) ringkas pada klien untuk memahami mengapa urutan penyisipan naif gagal di bawah konkurensi
  • Sebuah CRDT yang dibina dari awal untuk jujukan teratur, walau betapa tidak efisien sekalipun, untuk melihat sifat komutatif (commutative property) dalam tindakan
  • Integrasi berperingkat dengan permukaan penyunting kod sebenar, kemungkinan seperti CodeMirror atau Monaco, untuk menangani ketidakpadanan antara API imperatif penyunting dan sifat fungsian sejarah operasi

Setiap langkah akan disertakan dengan rasional bertulis. Mengapa pendekatan ini dan bukan yang itu? Andaian apa yang telah disangkal? Abstraksi apa yang bocor?

Pengajaran Sebenar

Memulakan projek seperti ini tanpa pengalaman dalam WebSockets atau CRDT adalah menakutkan, tetapi kepakaran selalunya hanyalah kekeliruan yang berulang dengan label yang lebih baik. Objektifnya bukanlah penamat yang pantas. Ia adalah sebuah sistem yang tingkah lakunya boleh diramal kerana setiap lapisan dibina dengan niat, bukannya diimport dengan harapan.

Jika anda pernah membina perisian kolaboratif sebelum ini—sama ada penyunting teks, alat reka bentuk, atau enjin penyinkronan keadaan permainan—kongsikan mod kegagalan yang mengejutkan anda. Jika anda juga sedang mempelajari sistem ini, ikutlah bersama. Kod akan tiba secara perlahan-lahan, dan ia akan kerap ditulis semula. Hari 0 bermula sekarang.