The alternative to doing multiple calls to read the corresponding entries from each reference, is to duplicate the data of the other entries under each reference.
It's a bit hard to reason in your abstract example, so let's pick a more concrete, simple use-case: a chat app. Say you have two-level entities: users, and chat messages. Each chat message is written by a user, and a user has a display name. In the most normalized data model this could be:
users: {
user1: {
name: "david s."
},
user2: {
name: "Doug Stevenson"
},
usere: {
name: "Frank van Puffelen"
}
},
messages: {
message1: {
message: "In real time you can't do a single query across multiple...",
uid: "user1"
},
message2: {
message: "In a very general sense, nested callbacks...",
uid: "user2"
}
message1: {
message: "The alternative to doing multiple calls...",
uid: "user3"
}
}
Now let's say that we have a use-case where we want to display the latest 10 chat messages, each with the name of the user who posted that message.
The above data works fine, but you will need to read the user names from the /users
node, pretty much how you're doing now. This is known as a client-side join, and is quite efficient since Firebase pipelines the requests over a single connection.
You'd could deduplicate the lookup of the names using a cache, since the user names typically change much less frequently than the chat messages. This reduced the overhead since you're only loading each user's data once, but the code can get a bit verbose.
An alternative is to duplicate the minimal data that you need for your use-case. This means your data model would look like this:
users: {
user1: {
name: "david s."
},
user2: {
name: "Doug Stevenson"
},
usere: {
name: "Frank van Puffelen"
}
},
messages: {
message1: {
message: "In real time you can't do a single query across multiple...",
name: "david s.",
uid: "user1"
},
message2: {
message: "In a very general sense, nested callbacks...",
name: "Doug Stevenson",
uid: "user2"
}
message1: {
message: "The alternative to doing multiple calls...",
name: "Frank van Puffelen",
uid: "user3"
}
}
Now you can display the list of the latest 10 messages with a single read. The cost is that you use more storage, but typically storage should be considered cheap. The disadvantage in code is that you now need to write the duplicate data, which is more complex. And of course the duplicated data could get out of sync.
All above approaches are valid. Which one you pick depends on the use-cases of your app, your comfort level in duplicating data, and how much you personally value things like the amount of code, bandwidth consumption, etc. There is no one-size-fits-all answer, so you will have to make your own call.
For some great reading/viewing on the topic, see: