Gerçek zamanlı iş birliği, perdeyi aralayana kadar zahmetsiz görünür. Bir kişi yazar. Bir diğeri üç paragraf yukarıdaki bir satırı siler. Üçüncüsü Stack Overflow'dan bir kod parçası yapıştırır. Bir şekilde belge tek bir tutarlı hale gelir. WebSockets veya dağıtık durum (distributed state) konusunda önceden deneyiminiz olmadan bu akıcılığı sıfırdan inşa etmek, pervasızca görünebilir. Aynı zamanda aslında öğrenmenin doğru yolu gibi de görünüyor.

Bu proje sıfırdan başlıyor. Ödünç alınmış hazır kod kalıpları yok. Zor kısımların otuz saniyelik bir montajla geçildiği cilalanmış YouTube rehberleri yok. Hedef, birden fazla kullanıcının aynı dosyayı eşzamanlı olarak düzenleyebildiği, birbirlerinin değişikliklerini ve imleçlerini gerçekleştiği anda görebildiği iş birliğine dayalı bir kod editörü oluşturmaktır. Buraya ulaşmak; taşıma katmanlarını, tutarlılık modellerini ve belgeyi bozmadan eşzamanlı düzenlemeleri birleştirmenin çetrefilli problemini çözmeyi gerektirecektir.

“Gerçek Zamanlı” Aslında Ne Anlama Gelir

Çoğu web uygulaması istek-yanıt döngüleriyle çalışmaya alışıktır. Bir formu gönderirsiniz, sunucu kaydeder, sayfayı yenilersiniz. Gerçek zamanlı iş birliği bu sözleşmeyi tamamen bozar. Her tuş vuruşu, genellikle milisaniyeler içinde diğer tüm bağlı istemcilere iletilmesi gereken ve anlamı koruyacak bir sırada ulaşması gereken bir olaydır.

WebSockets burada bariz bir taşıma seçimidir çünkü istemci ve sunucu arasında kalıcı, tam çift yönlü (full-duplex) bir bağlantı sürdürürler. Her birkaç saniyede bir “yeni bir şey var mı?” diye sorarak bant genişliğini boşa harcayan HTTP polling'in aksine, bir WebSocket açık kalır. Kullanıcı A bir noktalı virgül yazdığında, bu karakter bir mesaj haline gelir, soket aracılığıyla merkezi bir sunucuya gider ve ardından B ve C kullanıcılarına yayılır. Bu kısım nispeten basittir.

Zor olan kısım, B ve C tam aynı anda yazdığında ne olduğudur. Eğer her iki değişiklik de sunucuya neredeyse eşzamanlı olarak ulaşırsa, hangisi kazanır? Mesajları sadece geliş sırasına göre yayınlarsanız, karakter kaybı veya karışık metin riskiyle karşılaşırsınız. Basit (naive) “son yazan kazanır” stratejileri, niyeti göz ardı ettikleri için başarısız olur. Ben birinci satırın başına “hello” yazarken siz birinci satırın başına “world” yazarsanız, sonuç birimizin silindiği bir ç

Beklentiler dürüstçe ayarlanmıştır. Hiçbir şeyin çalışmadığı dönemler olacaktır. İlk deneme, metin değişikliklerini temsil etmek için basit JSON yamaları kullanabilir; ancak JSON'un "bir paragraftaki 5. indeks" gibi bir kavramı olmadığını, bu nedenle aynı indeksteki iki eşzamanlı eklemenin birleştirilmek yerine birbirinin üzerine yazdığını keşfedebilir. İkinci deneme, özel bir doğrusal geçmiş günlüğü (linear history log) oluşturabilir, ancak doküman büyüdükçe bu günlüğü yeniden oynatmanın bir Big O kabusu olduğunu fark edebilir. Üçüncü deneme, WebSockets'i yerel olarak çalıştırmayı başarabilir, ancak paket kaybı ve değişken gecikmenin kuralları yeniden yazdığı gerçek bir ağ üzerinde her şey dağılabilir.

Bu sürtünme asıl meseledir. Çalışan bir depoyu kopyalamak, kuyruğun neden o özel sırada boşaltıldığını veya sunucunun neden bir versiyon vektörü (version vector) tuttuğunu araştırmayı atlamanıza neden olur. Aynı bileşeni üç kez yeniden inşa etmek yavaştır, ancak çerçevenin ne yaptığı ile kendi mantığınızın neyi yönetmesi gerektiği arasındaki sınırı anlamanızı sağlar.

Bu sürecin dokümantasyonu bir başarı derlemesi olmayacak. Yanlış yolları da içerecek. Örneğin, mevcudiyet farkındalığı (presence awareness) oluşturmak —kimin çevrimiçi olduğunu ve imleçlerinin nerede olduğunu bilmek— metnin kendisiyle aynı tutarlılık modeline bağlı olduğunu fark edene kadar kozmetik bir özellik gibi görünür. Eğer kullanıcı A, kullanıcı B'nin imlecini 10. sütunda görüyorsa ve kullanıcı B dört karakter eklerse, o imleç nereye hareket eder? Doküman topolojisi konusunda ortak bir anlayış olmadan, mevcudiyet verileri gerçeklikten kopar. Bunu çözmek, imleç konumunu sadece sayısal dizinine değil, alttaki veri yapısının kimliğine bağlamayı gerektirir. Bunlar, sıkıcı oldukları için değil, önemsiz olmadıkları için eğitim videolarının üzerinden hızlıca geçtiği türden detaylardır.

Sırada Ne Var

Yol haritası, tasarım gereği oldukça yalındır. İlk dönüm noktaları şunlar olacak:

  • Gecikmeyi ve bağlantı yaşam döngüsünü bizzat hissetmek için karakter olaylarını yansıtan (echo) ham bir WebSocket sunucusu
  • Eşzamanlılık altında basit ekleme sıralamasının neden başarısız olduğunu anlamak için istemci tarafında basit bir string buffer
  • Değişmeli özelliği (commutative property) iş başında görmek için, ne kadar verimsiz olursa olsun, sıralı diziler için sıfırdan bir CRDT
  • Editörün emirsel (imperative) API'si ile operasyonel geçmişin işlevsel (functional) doğası arasındaki uyumsuzlukla uğraşmak için, muhtemelen CodeMirror veya Monaco gibi gerçek bir kod editörü yüzeyiyle kademeli entegrasyon

Her adım yazılı bir gerekçeyle gelecektir. Neden bu yaklaşım da diğeri değil? Hangi varsayımlar yanlış çıktı? Hangi soyutlama sızdı?

Gerçek Bir Çıkarım

WebSockets veya CRDT'ler konusunda deneyiminiz olmadan böyle bir projeye başlamak göz korkutucudur, ancak uzmanlık genellikle sadece daha iyi etiketlerle tekrarlanan bir kafa karışıklığından ibarettir. Amaç hızlı bir bitiş sağlamak değildir. Amaç, her katmanın umutla içe aktarılmak yerine bir amaçla inşa edilmiş olması nedeniyle davranışı öngörülebilir olan bir sistem kurmaktır.

Daha önce iş birliğine dayalı bir yazılım —ister bir metin editörü, ister bir tasarım aracı veya bir oyun durumu senkronizasyon motoru olsun— geliştirdiyseniz, sizi hazırlıksız yakalayan hata modlarını paylaşın. Eğer siz de bu sistemleri öğreniyorsanız, takipte kalın. Kod yavaş gelecek ve sık sık yeniden yazılacak. 0. Gün şimdi başlıyor.