The id can be used to look up a particular message in the replica which supports various business logic
When Alice adds a message optimistically to her replica, she can simulate almost everything in the final result except the id .
The id is an important part of the message identity as it assigns uniqueness to each message in the replica collection. The id is also an important part of the view creation logic as it is used as the key in the React render function that maps an array of messages to JSX.
Resolving conflict in the two different id versions should be avoided. We are venturing into dangerous territories when the clients are in the business of reasoning about the provenance of data in its local copy. This could introduce a leaky abstraction problem wherein the client needs to know the implementation details of the server (e.g., how an id is picked), which can cause the system to be fragile and error-prone.
There are two ways to avoid performing conflict resolution on the id . Choosing which approach to pursue depends on the constraints and non-functional requirements imposed on the project. In particular, this is a tradeoff between technical complexity on the back-end vs front-end.
Conflict Avoidance (server-side)
A server-generated id for message was a constraint for the offline-first chat app project. The chat app was originally built to not be usable while offline. Users could not create new messages to be queued for sending while they are offline.
If we were building an offline-first chat app from scratch, we could have totally avoided the two different versions of id by making the real id client-generated.
- For the new message, the client generates a UUID then send that to the server.
- The server implements format check, duplicate check, and date check on the UUID. If any of these checks fail, reject the message send request.
This approach does not alleviate the clients from tracking what’s real and what’s optimistic in their replicas but it significantly simplifies the replica implementation as it can be implemented as a growth-only set. A separate data structure can be used to track the outgoing messages that are not server-approved (e.g., a Set containing the UUIDs of messages in the outbox).
Conflict Avoidance (client-side)
This is the approach taken for the OkCupid offline-first chat app implementation. The general idea is to implement a policy for “merging” the server-generated id into optimistically added message in the replica.
Because the replica data is used for business logic, simply ignoring the server-generated id and only using tempId would cause problems when we need to make another mutation to the message (e.g., marking the message as read which requires updating a property on the message in the replica).
Because the replica data also drives the view, replacing the tempId with the server-generated id will also cause problems because the message id is used as the key by React to render the message. If we simply replace the tempId with the server-generated id , we are going to experience a very noticeable flicker where React will unmount the optimistically added message and mount flirttailla Malesian morsiamet the server-added message.
Sub-problem 4: Eventual Consistency
Replicas can become out-of-sync with each other during the collaborative editing session but we need to guarantee that the states kept in the replica will eventually converge.
- At t = T0 , Alice goes offline
- At t = T1 , Alice tried to send a messages M1 (send fails)
- At t = T2 , Bob sends M2
- At t = T3 , Alice goes online again. WebSocket is re-established
- At t = T4 , Alice sends M4