11 June 2014
A Strategy for Managing Record Retrieval in Meteor JS

When you build a meteor web application that interacts with a database, that means sending selection criteria from the client (subscribing) and using those criteria to identify and retrieve records on the server (publishing). Data sources are reactive, meaning that as the data is published on the server and the client receives it, uses of the data will automatically update.

It’s a beautiful solution, it provides a lot of return for less investment than other systems, and its all JavaScript (or CoffeeScript) - but there is a snag: the set of records returned by multiple subscriptions on to same database collection are not directly associated with their respective selection criteria. Or in other words, “When I asked for X and Y, and I got back X+Y, not X and Y separately.”

So What’s the Big Deal?

The big deal is that Meteor’s all about keeping things simple. Teasing apart sets database records when the search has already identified them seems like more doing more work instead of less!

In reality though, it really isn’t. The problem is the synchronous transactional mindset, the voice inside the head that says, “Ask for records, get the records, then proceed.” In Meteor, reactivity is used to handle data when it arrives, not when it’s requested, “Set things up, ask for records.”

Behind the Publish/Subscribe Curtain

What needs to be seen is that the Meteor publish/subscribe mechanism is not the whole picture - there are a few steps that are critical to understand. In fact, the process is most easily seen step by step.

1 - The client makes a subscription by specifying records to find:

Meteor.subscribe("foo",arg1,arg2)

2 - The server publishes the records specified in the subscription:

2a - first finding the records specified in the subscription,

2b - then returning the records found (and new ones as the database is updated:)

Meteor.publish("foo", function(arg1, arg2) {
  return Meteor.Foos.find({bar: arg1, mumble: arg2})
}

3 - The client finds the records published by the server (and new ones as they are published:)

Meteor.Foos.find({bar: arg1, mumble: arg2})

The critical steps are the find calls! Subscriptions and Publishings are effectively chaperoning these calls, but they aren’t doing the finds implicitly. That’s the beauty of starting a Meteor project with the autopubish package on and removing it once you have the logic of the program working - you usually just need to add the publish and subscribe calls. The find calls are already there.

So in effect, there is no teasing apart multiple subscriptions on the same collection. At each turn, the records need to be found in the collection that has been published to the client so that records from other subscriptions aren’t included in the set being sought.

Before leaving this point, consider that the same record may be requested by multiple subscriptions. In fact, consider that Meteor can be directed to send only the requested fields for a particular collection being publish. Meteor only wants to send a record once, so it sends the union of the requested records in a collection across multiple subscriptions, and the union of the requested fields for the records in the intersection of those subscriptions. Meteor is highly optimized to keep the cost of what’s being sent across the wire as low as possible!

DRYing the Code

Look back closely at the code bits above. The

Meteor.Foos.find({bar: arg1, mumble: arg2})

call is present on both the client and the server. This is commonly referred to as wet code - code that is not DRY - DRY meaning “Don’t Repeat Yourself.” Generally, it’s bad to say the same thing for the same reason in more than one place, because if that code needs to change, it has to change everywhere, and invariably one or two instances may be missed.

The Meteor solution for this is one of the best forethoughts that the Meteor folks had. Since both the client and server are written in JavaScript - in web apps, running on the browser for clients, and running in node on the servers, the Meteor policy is that unless code is explicitly targeted at the client or the server, it is sent to both by default.

Now, the use of the same code on both sides is kind of questionable - except in exactly this situation! When both the client and server both need the same search, this default strategy in Meteor is a big win.

So what’s done is to modify the code

Meteor.Foos.find(Meteor.Foos.fooSearch(arg1,arg2))

and define the new function in code shared by client and server

function Meteor.Foos.fooSearch(arg1,arg2) {
  return {bar: arg1, mumble: arg2}
}

This may not seem like a big thing, but besides being DRYer, if the code is complex - which it certainly can be - having one instance in one known place ends up being a huge win.

Takeaways

I personally like to have one file, in lib/collections.js (or .coffee) - in one place in my app - that declares all the collections and related searches that I use. I may spread this out in time, and break it into several files, or even one for each collection, especially if packages that include both database and code start to predominate. But for now one is good enough.