Saving data to your database
Room Service's maps & lists can be used as a collaborative "draft" you can use before saving a final copy.
The Shared Draft Pattern
In a single-player application, your user's data is stored in two places: the client and the server. On the client, you're storing working draft of the data, that might be overwritten later. And on the server, you're storing the main, authoritative copy of the data.
A user makes some changes to a document, and when they hit "save", the browser sends the current draft to the server for long-term storage.
But in a multiplayer application, the draft if shared between users, and is therefore stored in multiple places.
In this model, anyone can save, at any time. Room Service will sync drafts between users as fast as possible, but each client can save their current version to the server, at any time.
This might get weird, and that's okay.
What we're doing here is trying to force a traditionally ACID system (ie: your database) to play nicely with a BASE system (multiplayer). As you can imagine, this can get weird quickly. And in general, it's not recommended for mission critical information (billing, moon landings).
But... this is typically a fine strategy for most multiplayer applications. The draft pattern works well in practice, but not in theory. You're not going to pass a Jepsen test, but you'll ship a product folks love, on time and under budget.
To help, Room Service is built with this level of practicality in mind; whenever possible, we make the implicit edge cases explicit. But let's dive into what weirdness actually exists here, and how you can handle it.
The final version should get saved eventually.
This strategy's biggest weakness is that a user might "save" an old copy of the data to your database.
For example, say we have two users, who've both added some conflicting keys to their maps at the exact same time. Before we've sync'd over the network, each user looks like this:
1// Alice2map.get("tree") // "oak"3map.get("lunch") // "ramen"45// Berry6map.get("tree") // "spruce"7map.get("lunch") // "pizza"
Eventually, they'll resolve to a single version, with winners of the conflicts chosen automatically:
1// Alice2map.get("tree") // "oak"3map.get("lunch") // "pizza"45// Berry6map.get("tree") // "oak"7map.get("lunch") // "pizza"
Room Service will automatically take care of the conflict resolution and deterministically pick a winner for each key. But Room Service isn't magic; there's still a gap of time between when the sync happens and when you save.
Save often and explain what's going on.
Treat new information as if it's changed the working draft. If a user gets updates from another user, treat it as if the information hasn't been saved yet.
1room.subscribe(map, nextMap => {2 setSaved(false)34 // ... do something with nextMap5})
It's always best to over-communicate; show the user what's going on with UI effects.
Consider saving more frequently, both when the current user changes something, and when you receive updates. Typically it's best to use a debounce or a throttle.