Intention preservation is an obvious goal in a chat app because https://kissbrides.com/fi/haitilaiset-naiset/ the point of the chat app is to facilitate a conversation between two people where messages are created in response to other messages.
Alice asks Bob a question and sees that the question is optimistically added to her replica of the chat history. In the meantime, Bob is typing up a response to another question Alice asked a day before. If Alice is on a really slow network, Bob will not see her new question come into his chat history until some time after he sends his reply to her old question. Bob’s intent is for Alice (the remote site) to see the new message as a response to her old question but because of network latency, Bob’s message will be misunderstood by Alice as a response to her new question. Bob’s intention is not preserved.
How do we prevent this misunderstanding? When Alice gets the event announcing that a message has been added to the conversation by Bob, rather than simply adding the message to the end of her chat history replica, she can perform some comparisons based on the message timestamps to insert Bob’s message in the right place in her chat history. This is possible because the real order of the messages is determined by the timestamp, which is created by the sender client at the time of creation (when the message is sent).
Solution Design
There are many optimistic replication and multiplayer functionalities implementations out there in collaborative editing tools such as Google Docs, Notion, Figma, and Peritext.
Studying these reference designs helped us get some general ideas about how to design the architecture for our system but the specific architecture choice depends on the the specific usage patterns for our chat app.
- What is the nature of the data that is being collaborated on?
- Does it need to be real-time (Synchronous)?
- What kind of mutations are supported?
Replica Data Structure
- The Google Docs model (pictured left) supports a real-time synchronous collaboration style where every update to the document form a single linear timeline.
- Asynchronous collaboration (pictured right) requires a Git-like model where users can create a private copy (branch) of the document and merge their branch into the main branch when they are ready to do so.
Operational Transformation (implemented by Google Docs) and Conflict-free Replicated Data Types (CRDT) are the two most common classes for conflict resolution strategies.
In our offline-first chat app, we need to support real-time synchronous collaboration of a shared chat timeline. But the mutations are only adding (send a message) updating (reacting to a message and updating the read time of a message for read receipt).
The server and all the clients are managing their own copy of a growth-only set. The server propagates changes to the shared state to by transmitting the update operation. The server propagates changes to the shared state to by transmitting the update operation.
Operational transform implemented in Google Docs is overkill for our chat app use case. For a two-player chat app, we don’t need to worry about the case where two people are editing the same message property (e.g., body, reaction, read time) at the same time because that’s not a valid use case. OkCupid’s chat app does not even allow users to update the body of a sent message.
If the chat app supports more than two players, then we need to implement conflict resolution for reactions and read times on a message. While that’s a future use case we don’t have to address now, it’s worthwhile to make our solution design general enough to support an arbitrary number of multi-players to it can be easily scaled for that future use case (which we identified earlier as a likely future use case).