Transcript
  • Meteor
  • Tutorial

How does the Client Synchronize Writes with the Server?

In this video we're going to talk about how writes on the client are synchronized with writes that happen on the server. So to get us started, let me just show you a quick example that we'll be working with. So I have a collection called Items. And Items is what we would normally work with when, for example, we call find, or find one, or update, or insert. But Items has a private property on it called collection. This property is an instance of a local collection. And that's what's referred to as the local cache of the collection. It's also what the user interface is bound to, and what it responds to.

So for example, if I were to grab the first item I could update the local collection directly. So I'll call the update method here, and say that I want to update this item and I'll increment the counter by one. And you'll see in the user interface above that that one that's showing up in the browser there is the counter field of this document.

So if I update the document in the local cache, you can see the user interface updates right away. And I could do that again an increment the counter another time, and another time, and the user interface will continue to update as a result of the changes to the local cache. But what happens when I call Items.update and I don't deal directly with the local cache? How does Meteor write to the local cache and cause our user interface to update right away, and then synchronize the change that comes from the server?

Let's take a look. In the example that I just showed you we're working with a collection called Items. And we wrote directly to the local collection of that item by using the underscore collection property. And that's how we were able to update a document in the local cache, or the local collection. And we saw that the user interface responds immediately to those changes to the local cache.

But when we call the update method of items, if you recall this is actually calling a Meteor method. And so that Meteor method is going to work on the client and on the server. So on the client it's going to do some work and then it's going to make an RPC call to the server. So what happens first when the client method runs and it updates the local collection.

Well it turns out that there's a pretty neat mechanism that when you call a update or write method on a local collection from inside of a Meteor method on the client, the Meteor method is able to work with the local collection to keep track of what the original values of the documents were before the write. So the way that it does this is that when we call a Meteor method, beforehand it prepares all the collections to be ready to store original values before the writes happen.

And then what we make a write, like when we update this document here, this collection will store the original value in its own document dictionary of original values. And then when the Meteor method on the client side has completed, the method infrastructure will grab all of the original documents from all of the collections and store them in another object called serverDocs.

So when that process completes we'll see our user interface and it gets updated right away. And this is what sometimes is referred to as the latency compensation. And then the Meteor method for the update call on Items will send an RPC message to the server. Now the server side of that method call will also save the document to, or update the document, but this time it'll be updating it in Mongo.

Now in the meantime, we might get a bunch of changed messages from the server for this document. And no matter how many we get here the document that's going to be updated is not the one in our local collection but the one that's stored here. So once on the server side, the Meteor method has written the document into the database it will send out changed messages to all the connected clients, including the client from which we sent the original RPC.

And then that document will get updated in this special object of serverDocs. And when all of these writes have completed the server will send another message which is called updated, which is a little confusing, probably easier to think of it as done. With the ID of the original method that made the call.

And when the client get this done message it takes the document from serverDocs and updates the local cache with that value. And then we'll see our user interface update again if any of the values have changed. And we'll see it updated with whatever the result from the server was. So let's take a look at an actual example.

In my sample code js file, you can see I've declared the Items collection up here and I have a Meteor method called incrementCounter. And because I've declared it outside of the isServer and isClient block it'll run in both places, just like the items.update method will. Inside my incrementCounter method you can see that I check if I'm on the client, or in other words I'm running in the stub, or this is the simulation, then I'll update the local collection, or the local cache. And I'll even set a property here that says that it's been updated from the client. And then I'll just return.

If I'm on the server, to start us off I'm just not going to do anything. Let's just return undefined. Down on the client, what I'm going to do is subscribe to the Items publication. And once all the documents are ready and received on the client, I'll call find and then observeChanges. And this is so that in the console we can see how the underlying local collection documents are changing. And I'll just print to the log that the document is changing. And so you can think of this observeChanges as a similar mechanism to how the UI is responding to local collection changes.

And then I've created this helper method here called doIncrement and what it's going to do is grab the first item in the collection. There should only be one. And then it's going to make a Meteor method call to incrementCounter. So this would be similar to calling items.update and then I'll just print out the response when I get one.

And then right after that, I'm going to just start writing to the local collection. So I'm going to increment the counter a bunch of times. OK, before I make the doIncrement method call, keep your eyes trained up here in the browser. So take a look at this one and watch, it should flicker a bit as we mutate the local collection.

What I'm going to do is down in the console, I'm going to call the doIncrement method that I created. And you see a little bit of flicker at the top until we get the result from the server. And then it's set back to the original value of one. Now let's look down here in the JavaScript console. You can see what happens is our Meteor method call gets called, and it gets run on the client first. And so the counter gets incremented to two. And then we also added this extra field called updatedFrom and set it equal to client. And that'll happen inside of our method.

And then after the method call, I mutate the local collection a bunch of times incrementing the counter by three, four, five, up to seven. And so during this time, when you're seeing the user interface updating a bunch. And then we get the result from the server from making the RPC call, the Meteor method returns. And we see that once the method is marked as being done, we didn't actually get any result from the server. And so what Meteor will do is take that server document that we saved away earlier. And say, it didn't get anything from the server so just roll back the change.

And so you can see what it does is it then changes the local collection to reset the counter back to one. And the updated field get set to undefined. And that's because we didn't actually get a change from the server. So let's fix this by copying the code from the simulation and moving it down into the server side as well.

So now we're going to update on the server this document and increment the counter. And then the updatedFrom we'll say is from the server. And we need to make our selector a little bit more strict because we're on the server here. And now over in the browser, let's call the doIncrement function again, and take a look at what happened.

So when we made the doIncrement call, the first thing that happens is we call our Meteor method and the client side of that Meteor method runs. And so you can see the local cache, or the local collection, gets updated with the counter value of 2, and an updated field that says it's from the client. And then it sends off the RPC call to the server.

Then right after that I make a bunch of update calls directly to the local collection. And so this would cause a little bit of flicker up here and you'd see that the user interface is updating a bunch of times. And then we get the result from the server. More specifically, we get a changed DDP message that says that, this document has been changed and the value of the counter is two. And you can see that the updated field gets set to the server. And that all happened on the server side of our Meteor method call.

And then we'll get another DDP message saying that the Meteor method call is complete. It's done. And at that point, Meteor will take the server document that was set here and update the local collection with this document. And that's why, ultimately, in the user interface you see that we're showing a two in the server and all of these changes were just discarded.

And so, hopefully, this gives you a better sense of how Meteor is synchronizing writes that happen on the client with those that happen on the server. We looked at when you make a write to the local collection on the client it's reflected in the user interface right away. But we also store away the original value of the document into the special object called serverDocs.

And then every time the server sends down a changed message for that document, we don't actually update the local collection. Instead, the document that's stored here gets updated. And then when the server sends down a message to say that the Meteor method has completed, this is called an updated message if you look at it in WebSocket Inspector. But it's basically indicating that the original RPC method is done.

Then this document here will get merged back into the local collection and then you'll see the user interface reflect this latest value. So that's how the client is able to stay in sync with the document from the server.