[轉載]構建Web實時協作文本編輯器
原文:
https://medium.com/@david.roegiers/building-a-real-time-collaborative-text-editor-for-the-web-draftjs-sharedb-1dd8e8826295
為Web構建自己的協作文本編輯器變得相當可行。在豐富但混亂的javaScript世界中有許多不同的方法:本文就是其中之一。如果您對我們的設置感興趣或喜歡有關kamikaze模式的開發人員的故事,請繼續閱讀。
幾個月前,我和約翰內斯·魏斯和費利克斯·加斯特在周三晚上坐在會議桌旁。這是我們創業公司Conode的每周Jour Fixe,這是一個幫助團隊組織會議的生產力SaaS。
我們的銷售線索和用戶希望協同編輯頁面 - 您知道,Google Docs風格。我們最初認為這是一個太大的挑戰,因為我們缺乏外包預算和內部專業知識來自己實施。直到那天晚上,我們意識到這對我們的生存至關重要,所以菲利克斯和我勇敢地說
接受挑戰!
我說勇敢,因為我們剛剛從一個名為Thinslices的主管機構接管了代碼庫。我們真的不知道開始時的技術堆棧。所以,它承諾是一個顛簸的旅程...然后,這就是我們喜歡他們的方式。
理論
在進入代碼之前,我們需要談論理論。這個分布式系統的復雜性不容小覷,因此高級概述將有助于了解正在發生的事情。
文本編輯器的行為和外觀可以像快照一樣在任何時間點提取并存儲在簡單的javaScript對象中。我們稱之為文檔?狀態。
DraftJS可以將我們的Conode頁面的文檔狀態呈現為一個簡單的Javascript對象。
為了進行協作,必須通過在不安全的網絡之間發送消息來在多個對等體之間共享該文檔?狀態。需要一個協議來正確管理它。
等等,究竟需要管理什么?為什么我們不能在有人編輯某些文本時立即發送狀態對象?
在整個過程中用簡單的問題挑戰自己是一種很好的做法。它有助于解決問題。
好吧,想象兩個用戶同時輸入內容。在這種情況下,
兩個客戶最終都會有不同的狀態,并且
其中一個更改將被覆蓋。
此方案會導致不同的最終結果和覆蓋操作。
這是一個糟糕的用戶界面,所以我們絕對想避免這種情況。
上述兩個問題對應于我們的協議需要滿足的兩個主要技術條件:
融合?:所有編輯者必須在有限的時間內收斂到相同的文檔狀態。
并發?:并行發生的編輯會產生正確的最終結果,與執行順序無關。
這有點簡化。研究論文將討論最終的一致性,交換性和冪等條件,對中央服務器的需求,...所有這些學術文獻都提出了過多的協議和算法 - 一些比其他協議和算法更合法(見下文)。為了簡明起見,我們不會深入研究這個問題,只是說它們可以分為以下兩類中的任何一類:
操作轉換(OT)將文檔狀態表示為一系列操作。每個操作都是在本地快照之上創建的。現在,假設該操作被發送給同時進行編輯的對等體。該對等體將具有不同的快照,因此首先需要在應用之前轉換操作。這是OT如何運作的本質。
無沖突復制數據類型(CRDT)比OT復雜一點。它使用更多的內存和帶寬,但反過來保證了最終的一致性,而無需中央服務器。所以,你可以說它在理論上更完整。
我們選擇從OT開始,因為(1)它是最受歡迎的,(2)我們找到了一個很好的javascript庫,名為ShareDB,提供開箱即用的功能,(3)我們并不真正理解我們是什么這樣做。
我們很高興最終發現它是正確的選擇:-)
邁向統一的運營轉型理論和CRDT
更新(2017年3月2日):現在有了這篇文章的工作代碼,并進行了額外的優化。medium.com
前端
Conode是一個單頁面應用程序,它使用React + Redux。文本編輯器基于著名的Draft.js框架。它沒有提供太多開箱即用的功能,但根據他們自己的說法“在Draft.js中,一切都可以自定義。”
看起來如何 - 試試conode.io吧
問題是Draft.js不是用于協作編輯。這與它的API主要暴露狀態而不是操作的事實有關。社區實際上似乎在這個問題上存在分歧。最后,無論是否可行,取決于您的功能和性能要求。
還有其他Javascript編輯器,如Quill,可以更好地處理實時協作。在我們的例子中,我們已經有一個高度定制和代碼密集的編輯器。重建它需要花費太多時間。既然我們知道社區中的其他人已經完成了這項工作,我們決定抓住機會并建立起來。
這個調查
要連接DraftJS編輯器以進行協作,我們需要Web套接字。這項技術允許我們以較少的開銷向瀏覽器發送消息(雙向),這是傳統HTTP無法實現的。
到目前為止的消息傳遞?但是,那個處理那個花哨的OT協議的應用程序層呢?經過大量的研究,主要包括閱讀無數的Github問題,并且無可否認,使用Chrome開發者工具的網絡選項卡調查現有應用程序,ShareDB是獲勝的選擇。
通過簡單地觀察其他解決方案,您會驚訝地發現自己能學到多少東西。(Ben Affleck,Paycheck)
ShareDB是一個庫,它在服務器上存儲javascript對象,并使用Web套接字在多個客戶端上共享它。因此,如果任何客戶端傳遞操作,ShareDB將自動通知其他訂閱的客戶端。
原型
是時候開始編碼了。首先,我們創建了一個將Draft.js與ShareDB相結合的簡單原型。這樣就可以快速測試我們的架構,而無需面對將其構建到現有代碼庫中的復雜性。
我們的原型架構。我們使用Editor組件的'onChange'和'value'支柱作為受控的React組件。我們在那里結合傳入和傳出操作。
請記住,我們說Draft.js不公開操作,只公開EditorState。但是,OT與操作一起工作?......為了解決這個問題,我們使用了json0-ot-diff,這是一個將先前狀態與新狀態進行比較的庫(使用convertToRaw)。這為我們提供了一個JSON類型的OT事務,然后我們將其傳遞給ShareDB。
這樣的計算在性能方面是昂貴的,但最終結果就像一個魅力。如果您希望收到該原型的副本,請隨時與我們聯系。
整合
下一步是將此工作解決方案集成到我們現有的代碼庫中。這帶來了挑戰 - 超出了我們的預期。
要在我們的前端管理文檔狀態,我們使用Redux。因此,我們需要在Draft.js,Redux和ShareDB之間管理EditorState的單一事實來源。最后,我們構建了一個函數和事件循環,可以在下圖中看到。
傳出操作的事件循環。傳入操作的處理方式類似。
我們的文本編輯器React Component,包含Draft.js,有一些競爭條件。在單用戶模式下,這些都不是問題。一旦用戶開始同時進行更改,偶爾的編輯就會被覆蓋。很難檢測到模式,當我們修復模式時,會觸發新的錯誤。
ShareDB將每個更改存儲為其數據庫中的操作。在我們創建用于實時協作的文本編輯器時,這相當于大量操作,這將對存儲容量和計算能力產生不利影響。因此,我們在REST API工作流程之上構建了一個協作服務,系統地清空自己。這使得存儲操作的數量保持最小,并將協作的復雜性提取到獨立的微服務中。
我們后端的簡要概述:添加了彩色部分以進行協作。在單用戶模式下,使用正常的RESTful API。只要與多個用戶共享頁面,通信就會切換到Web套接字。
我們的編輯器是一個非常大的React組件。超過1000行......為了不在無盡的重構努力中迷失自我,我們首先考慮創建一個更高階的組件,這將為現有的編輯器增加協作風格。最后,將我們的協作邏輯放在從我們的編輯器處理更新的redux動作創建器中更簡單。
為了避免故障,需要涵蓋許多邊緣情況。例如,當wifi丟失時自動Web套接字重新連接,檢測死Web套接字客戶端,在用戶轉到儀表板并打開另一個頁面時正確打開/關閉ShareDB訂閱等。
結束
最終的結果是工作,但由于子-彈點2的競爭條件而留下了一些故障。這些漏洞非常困難,我們決定不再因為客戶截止日期而失去任何時間。作為臨時解決方案,我們對整個頁面進行了鎖定,可以請求并將其從一個用戶傳遞到另一個用戶。
不可否認,最終解決方案并不完美。但是,現在我們知道它的工作原理以及需要進行哪些重構才能使其發光。
原型設計確實可以帶來回報,因為它可以快速驗證您的架構。沒有它,我們從來沒有這么遠。
計劃更多時間重構代碼。
一些理論在分布式系統中有很長的路要走。即使ShareDB是開箱即用的,理解背后的模型也是必要的。
我希望這篇博文能夠為團隊提供洞察力,為團隊開發他們的第一個實時協作文本編輯器。如果有,請告訴我。如果它不...謝謝你,再來一次。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。