When thinking of modern applications, we have come to expect certain features: global availability, accessibility through web browsers and mobile devices, and a user experience that is both intuitive and efficient …
For those of us who have worked on distributed teams—due to geographic distribution, working preferences, or the size of our organization—the impact of collaborative features on productivity is well understood. That is why our expectations of modern applications extend to: being able to share our work with colleagues, receive immediate feedback without having to switching to another app, and collaborate on projects in real-time.
Various terms have been proposed to describe the shift from single-player to multiplayer tooling, including: “real-time multi-user collaboration”, “multiplayer collaboration”, “deep collaboration” and “collaborative enterprise.”
Regardless of the terminology, it’s undeniable that real-time multiplayer / multi-user collaboration is now table-stakes. The benefits to team alignment, communication and consensus alone are exceptional. When individuals can work together in a shared space, using a common language and framework, productivity increases as collaboration becomes seamless.
Just think of how painful and slow collaboration used to be when everyone worked individually, in silos, on the same project. We would pass a project file back and forth, with our individual additions and changes. And then somebody would have the dubious pleasure of reconciling (if at all possible) all the work of the entire team, and producing the “final_final” version of the document.
Beyond improving team alignment, real-time multi-user collaboration serves as a powerful virality machine for businesses and an efficient monetization strategy. The ability to invite and involve more stakeholders in a project ensures that (1) more people will use your product to drive a “bottoms-up” sales movement, and (2) you will get higher adoption and retention rates within organizations.
It’s not a coincidence that Figma, Miro, Canva, Notion, Google Workspaces, etc. have been so popular and successful in the past few years. And beyond them, many SaaS businesses, even companies that haven’t traditionally thought of themselves as collaboration-first, have embraced this trend.
Real-time Collaboration in Software Development
Developer tools are one of the verticals where real-time multi-user collaboration is particularly a good fit. In fact, it is rapidly becoming a must-have requirement.
Software engineering inherently demands collaboration for coordinating efforts, integrating software components, and ensuring the final product is both cohesive and functional.
Furthermore, organizations are realizing that all aspects of software engineering require a collaborative approach, including system architecture. In fact, the evolution of the Software Architect role from ‘Ivory Tower Architect’ to viewing architecture as a 'team sport', highlights the necessity of diverse input and contributions for a successful system design.
There are many benefits when a team collaboratively engages in designing the system architecture. Most obviously the fact that a team-wide grasp of the system fosters informed decision-making and reduced misunderstandings.
This approach not only leads to a more cohesive final product but also enhances the system's robustness. By understanding the system's structure and dependencies, the engineers can pinpoint optimization opportunities, improving resource efficiency and overall performance.
When evaluating which application features to build, an obvious question to ask is “is there a less effective solution already in place that we could improve upon?” In the case of real-time multi-user collaboration, the question to ask is “are there signs that collaboration is already taking place, albeit inefficiently?”
It is obvious that collaboration around system design is currently happening - therefore making it a prime candidate for the introduction of a tool that leverages real-time multi-user collaboration.
This why when our team built Multiplayer.app - a system design and architecture documentation tool - we made collaboration a fundamental requirement from the outset, integrating it as a core feature of the application. This blog post will delve into our approach to this feature and the solutions we implemented.
Multi-user Collaboration in Practical Terms
When designing Multiplayer we wanted to enable our users to experience fully instant and simultaneous collaboration. This means that every user can edit anything at any time, with all edits being immediately synchronized and any conflicts automatically resolved.
This concretely translates in these feature sets:
(1) Awareness or Presence: These features enable the real-time tracking and display of user status (i.e. avatar stack, live cursors, user in-app location, typing indicators).
(2) State Synchronization: This ensures that all user actions and changes are accurately synchronized and instantly propagated to every user (i.e. live updates, co-editing, undo / redo).
(3) PUB/SUB Messaging: This allows for the efficient delivery of messages to the appropriate user, in real-time (i.e. comments, push notifications)
The implementation of real-time multi-user collaboration presents significant challenges, which have been explored by computer scientists for decades.
Two notable algorithmic solutions have emerged: Operational Transformations (OTs) and Conflict-Free Replicated Data Types (CRDTs).
Of course, it’s also possible to use websockets or, depending on your problem space (and the desired behavior for different states) opt for a custom approach inspired by the traditional algorithmic solutions. For example, you might get inspired by multiple separate CRDTs and use them to create the final data structure that best represents your document (similar to the approach Figma took).
After researching all the various solutions, we opted to use a two fold approach:
Websockets: For simpler features such as the avatar stack or commenting, that don’t require conflict resolution and can have only a single owner, we opted to extend an existing RESTful API and simply broadcast request and response messages to all connected clients using websockets.
CRDTs: For more complex features that require sophisticated conflict resolution due to concurrent inputs from multiple users (e.g. co-editing) we chose to implement a widely-used open-source CRDT implementation called Y.js.
Easiest and Fastest Way to Implement CRDTs
Our investigation identified CRDTs as the optimal solution for conflict-free collaborative features, due to their simplicity, speed, and robustness. In comparison, Operational Transformations (OTs), the technology behind Google Docs, proved overly complex for a startup prioritizing rapid feature deployment.
CRDTs are categorized into two types: operation-based CRDTs (or Commutative Replicated Data Types) and state-based CRDTs (Convergent Replicated Data Types). Both variants ensure strong eventual consistency, meaning that despite temporary connectivity issues or high latency, all connected clients will ultimately converge to a consistent final state.
CRDTs encompass a suite of straightforward algorithms, including last-writer-wins registers, grow-only counters, positive-negative counters, grow-only sets, two-phase sets, last-writer-wins element sets, and sequence CRDTs, to name a few.
Our choice of Y.js was driven by the compatibility its data structures such as documents, maps, and arrays, to conventional JavaScript data types, which facilitated the integration into our application without significant refactoring.
Y.js stood out for several additional reasons:
- An extensive array of open-source integrations with code editors, whiteboard apps, and rich text editors
- The capability to support any data structure through Y.js Shared Types
- Seamless client reconnection without data loss
- Network agnosticism
- Built-in, user-friendly “awareness” (user presence) features
- A large and active community
To illustrate the implementation of Y.js CRDTs in Multiplayer, consider the scenario where two users edit the platform system architecture simultaneously.
While one user (Client 1) relocates a collaboration service within the architecture, another user (Client 2) might be renaming it. Y.js ensures that these concurrent changes merge seamlessly in the final state.
While the integration of Y.js into our platform was largely straightforward, we encountered some notable challenges:
(1) Keeping Track of Order of Elements
Y.js supports arrays, but maintaining the order of elements requires additional effort. A naive approach might involve assigning an integer to represent the order of each element.
However, this method is impractical for long lists: whenever a new element is inserted you have to change the order value of every single element that follows it.
Our solution was to adopt fractional indexing, similar to the method used by Figma. This approach allows for the insertion of an element without the need to adjust the positions of subsequent elements.
To implement fractional indexing effectively, we utilized an arbitrary precision library instead of the standard JavaScript number type, which is a 32-bit floating-point number with limited precision. This choice avoids the precision limitations inherent in the built-in number type, ensuring scalability for lists with frequent insertions.
(2) Supporting Cross-Document Changes
Y.js does not inherently support changes that need to propagate across multiple documents. To accommodate inter-document dependencies and ensure seamless updates, we established connections between various data structures within our application. By monitoring changes in the Y.js document, we could automatically synchronize updates across related documents.
For instance, if a user renames a component from "web-api" to "auth-api" within the platform architecture panel, this change is instantly reflected in all instances where the component is referenced, such as in the component list panel or on the individual component page, without the need for manual refreshing.
It’s Not Just Data Types, There’s System Architecture Too
Although Y.js supports a peer to peer system design, where you don't need a central service, you need to also consider the best system architecture that would serve your business model.
For instance, a critical requirement for our application was the ability to have version control for system architectures. This necessitated the implementation of a snapshotting functionality, which in turn led us to introduce a central 'peer' within our architecture, named the 'collaboration service'.
Leveraging the Y.js library, this service resolves conflicts and provides the definitive source of truth for our data. Specifically, the collaboration service is responsible for:
- Providing the most recent version of the document to newly connected clients.
- Sharing edits among all active clients to ensure real-time synchronization.
- Creating and storing document snapshots for version control.
- Controlling additional required actions seamlessly(create links, validate content)
Moreover, to enhance the efficiency and reliability of our collaboration infrastructure, we integrated a directory service cluster. This component is tasked with:
- Determining optimal locations for hosting collaboration sessions.
- Monitoring active sessions.
- Seamlessly transferring sessions as necessary to balance load and optimize performance.
These architectural decisions were instrumental in enabling seamless real-time interactions while accommodating essential features such as version control. However, as with any system architecture, ours is also continuously evolving!
To scale the collaboration service, we plan to decouple the functionalities for which the “collaboration service” is responsible, moving the snapshotting to a dedicated service. We want to move from a stateful to a stateless architecture (possibly leveraging Kafka or RabbitMQ) to share user edits as fast as possible and to ensure continuity of service in case the “collaboration service” goes temporarily down.
Conclusion
To summarize, integrating real-time multi-user collaboration into your application offers three key advantages:
- Enhanced User Experience: This translates to a faster 'time to fun' for users, as they can align, communicate, and reach consensus with minimal effort.
- Shorter Production Cycles: By ensuring that users have a clear understanding of expectations, responsibilities, and the project roadmap, you can avoid miscommunications and unnecessary revisions. Collaborating in a unified space also minimizes context switching and boosts productivity, leading to a quicker 'time to market'.
- Increased Business Revenue: When your tool becomes integral to the workflows of entire teams or organizations, you benefit from higher adoption and retention rates, positively impacting your revenue.
Our teams’ commitment to seamless alignment and real-time visual collaboration not only influenced our name but also catalyzed extensive research and experimentation in developing collaborative features using a lean and open-source-oriented tech stack.
Two key takeaways from our journey are:
- Leverage Existing Solutions: There's a wealth of tools available for incorporating real-time collaboration into your app. While we chose Y.js for in-house development, other options like Liveblocks or Ably offer SaaS solutions. Don’t reinvent the wheel!
- Prioritize Collaboration from the Beginning (if you can): Integrating real-time collaboration features at a later stage or only to a part of an existing product can be complex and less effective. By considering these features in your initial design phase, you ensure a cohesive user experience and make technological choices that support the ongoing development of these capabilities.
We hope our experiences and insights inspire you to explore how you can integrate similar functionality into your applications to enhance collaboration and productivity.
Top comments (0)