tmbb
Realtime collaboration
I’ve been thinking of realtime collaboration on Phoenix. The most obvious case is text editing (both rich text and plain text) bit there is a lot of potential for rocher datatypes, like JSON documents.
SharDB is a Javascript framework running on NodeJS that allows for real-time collaboration in editing JSON documents, with facilities for text editing as a special case. It uses Operational Transformations (OT) with a client-server architecture (not peer-to-peer, that doesn’t really work very well in real life). I think it would be cool to have this in Phoenix.
Currently, there are Elixir libraries that handle OT for plaintext and rich text, although none of those libraries ship with a production-quality server or client. The library for plain text OT does ship with a server, but not with a client, and the implementation of the server is not complete.
There isn’t yet an implementation of OT for JSON documents in Elixir, but the implementarion used by ShareDB seems easy to port (the transformation function is quite stupid and inefficient, but it seems to get the job done).
Instead of OT, some people advocate CRDTs, but they have some important disadvantages in practice. They have a much higher memory overhead (and sometimes a higher bandwidth overhead) and they don’t resolve conflicts. OT with a central server has low overhead and gives us a canonical document (the one that lives on the server) at each moment in time.
This leaves us with the taks of:
-
Writing a client using something like Phoenix channels
-
Implementing the network protocols. It’s possible to opimize them a lot when compared to what ShareDB does. ShareDB uses JSON, which for small events like keypresses wastes bandwidth like crazy. Some easy savings could be achieved by using MsgPack instead of JSON, but I suspect we can do better with even more compact transmission formats.
-
Porting OT for JSON documents from ShareDB. This is important because JSON can describe a lot of types of variable length documents.
-
Writing a generic OT server onto which one can plug the different OT types. This part is independent from phoenix, even if it ends up using Channels to communicate with the outside world.
What would this be useful for? Well, first for “normal” collaborative editing. Think of a document that can be edited by more than one person. Or even a JSON tree of documents that can be esited by more than one person.
But there are other advantages. Think of something like Drab. Drab.Live makes it possible to sync client and server. But it can get you in problems if you edit the state on the client and server concurrently. You get the semantics not of collaborative editing but of Last Write Wins, which is not desirable. If you have OT, you can edit the state on the client and server concurrently ( the fact that other users can esit the state too is a just a nice bonus). This could pave the way for real isomorphic apps in which the state is shared by the client and server and can be operated on by both of them.
Elixir makes it easy to deal with the kinds of servers required here (just spin up a genserver per document and listen to operations, possibly persisting the oeprations womewhere, like an ETS table). The main obstacle here is the fact that a lot of code still needs to be written. I wonder of users here would like to collaborate on this.
Trending in Discussions
Other Trending Topics
Latest Phoenix Threads
Chat & Discussions>Discussions
Latest on Elixir Forum
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #performance
- #security











Most Liked
houshuang
Hi,
we’re building a collaborative learning platform in Meteor/React (FROG), where we’re using ShareDB heavily - both ot-json as the backing store for collaborative activities (activities are pluggable, and they get access to a shared document, which they can structure as they want - this gives us a lot of flexibility), and ot-text for collaborative text editing. We are having some problems scaling up (both because of Meteor and possibly ShareDB), and I dream of having a “OT as a service” thing, where I could just run an Elixir server or cluster, which would be compatible with the front-end ShareDB client (or something similar). All the JS code could be kept - and it would be super-fast, scaleable etc.
I did try a few years ago to rewrite the login in ot-text to Elixir, it was an interesting exercise… It’s pretty functional code (all functions which take an input and produce an output without side-effects), but they use closures a lot, which I changed to recursive functions. I even set up a way to run the (very extensive) JS test harness for ot-text against my library - and it got pretty far. I never fixed all the failing tests, and I kind of abandoned it, because I didn’t have the push to move it forwards (I was in the middle of running experiments for my PhD thesis, and didn’t actually need it. My very old abandoned attempt.
I still think OT is amazingly powerful, and it seems tailor-made for Elixir… I would love to see someone try again to make a production-ready OT server in Elixir - having a compatibility mode with ShareDB would let a lot of products switch easily, and would work well with existing extensions like Rich text etc, but investigating a more efficient transmission format would be interesting too…
Note that the ShareDB people have been working on a new version of ot-json for years, which apparently is soon ready.
If you pursue this, please let me know, as I’d be very interested to follow along, and maybe use it. We’re also working on collaborative writing analytics (predict collaboration quality etc), if anyone is interested.
best
Stian Håklev
Ecole Polytechnique Fédérale de Lausanne
tmbb
Back from the dead. As part of the plan of writing a ShareDB backend in Elixir (so that we get all the real-time collaboration features for free), I’ve had to solve the problem of protocol mismatch between Phoenix and the ShareDB client. The ShareDB client is supposed to work with raw websockets, while Phoenix can only work in Channels. You can’t use raw websockets with Phoenix without getting yourself in a world of pain.
The obvious solution is to implement a websocket on top of Phoenix channels. If this makes as little sense as it made to me at the time, read on. The trick is to write a Javascript object that implements the websocket API but sends and receives messages over a Phoenix Channel.
I’ve ended up with something like this: main.js · GitHub
As you can see there, I build a
PhoenixWS, which is an object that talks like a websocket but sends and receives messages over a Phoenix channel:It takes as arguments a phoenix
socket, a channel topic and an initial payload (which is useful for authentication, for example). This is the only phoenix-specific part of that file. From that point on, the javascript code is written as if it were a normal websocket. The rest of the code in that file was actually copied with no changes from a random Javascript “chat server” implementation, meant to work with a websocket on top of NodeJS. This chat server now works with a Phoenix backend (see here: GitHub - tmbb/phwocket_example: An example using PhoenixWS · GitHub)The implementation is not complete and this is not production-ready yet, but it shows how easy it is to achieve some basic compatibility with raw websockets while being able to use Phoenix channels underneath.
The Elixir side is actually quite easy too. You add your
PhoenixWS.Channelto your socket:The code that implements the websocket channel is actually pretty easy to write:
Although there are still some holes in the websocket implementation (it doesn’t close properly, for instance), this can serve as a bridge between the ShareDB client and a backend that runs behind Phoenix. I imagine that the
PhoenixWSpackage might be useful by itself in projects where you want to interface directly with a Javascript library that uses websockets.Expect more developments soon, but not thaaat soon.
tmbb
After a long time, I’ve decided to go back to realtime collaboration on Phoenix. The plan is to implement something compatible with
ShareDB’s protocol. There are already some tools usingShareDBin the wild, and the most important case (collaborative editing of text) is already covered by ´ShareDB` pretty well (there’s a demo under 20 LOC doing exactly that with some extensions).ShareDBhas a client and a server implementation, both written in Javascript. In an ideal world, I’d port the protocol to Elixir and reuse the client without requiring any changes. However, the protocol expects messages to be sent through “bare” websockets, and Phoenix expects messages to be sent through “channels”. Channels are ingrained so deeply in Phoenix that I don’t think it’s possible to use bare websockets wthout reimplementing the whole channel machinery. In fact, Phoenix doesn’t even know what an websocket is.The client-side API for ShareDB starts with something like this:
The
socketreally must be aWebSocket, or at least something that implements the websocket methods. That’s actually very easy with Phoenix channels: you just have to create an object (class, prototype, whatever) which implements those methods and behaves as a webocket. However, there is a problem.ShareDBallows you to create several “documents”, which can be updated in parallel. That means each document should live in its own process and be connected to the client though its own topic. Our “custom” websocket implementation would have to route each message into the appropriate phoenix topic. But theWebSocketobject is sent raw JSON and doesn’t know which document/topic the message refers to. The only way to know that is to parse the JSON again and extract the document’s ID, which is unacceptable.This means I will have to rewrite both the
Connectionobject and theWebSocketobject so that theWebSocketis compatible with Phoenix channels and theConnectionobject can send objects into theWebSocket(so that it can route the messages into the correct topics).Last Post!
tmbb
It seems like Yjs is getting really capable, and even the author of ShareDB supports CRDTs inspired by Yjs as being the future of real time collaboration on the web. Has anyone looked into implementing am Elixir backend for Yjs?