Firestore — Data Modeling with examples
By: Guy Landoy
Tags: Data Modeling, Firestore, Data, Database, Architecture

#4

Store events likes as seen in example #2 and #3, in their sub-collection.

Store user likes in a sub-collection but not as a like per document as seen in example #3, but in an array of likes per document.

I’ll explain.

User sub-collection of likes arrays

In examples #1 and #2 we saved users likes in an array inside a document.

This is efficient when we need to check for events the user liked cause for

one read only we can get up to 3k~ likes of the user.

As mentioned before this had a small problem of scalability, what can we do if a user liked 5k events for example?

Solutions I had in mind for this issue:

1. Limit likes to 2k (for example) and delete old likes that are probably irrelevant anymore to keep likes by user under the limit.

2. Create a sub-collection for likes but the document will have an array of likes and will act the same as examples #1 and #2. Implement a logic to open a new array of likes (in a new doc under same sub-collection) when the array of likes fills up.

Conclusions:

Solution 1 can potentially make me lose data, but it is pretty easy to implement with a scheduled function if I save a timestamp for each like.

Solution 2 is actually a mix between the user likes in examples #1 and #2 with user likes in example #3. It is fully scalable if implemented correctly.

The problem with this solution is that it is very complicated to implement.

The implementation is complicated because when adding a like it needs to see which doc it should put the like into (the latest one) and when a doc gets full (you can decide on a likes limit yourself) open a new doc and “continue” putting likes there.

I picked Solution 2.

Pros

Scalable

Few reads for user likes

Cons

Complicated to implement

Summary

This solution is scalable, yet keeping the reads low.

It is more complicated to implement but more versatile.

I like this solution because even though it is hard to implement,

it is not required to implement the logic of the doc updates to start “rolling”.

Without implementing solution 2 at all this example is almost identical to example #2 with one document only that has an array of likes.

I remind you that this example’s con was the user likes scalability which becomes a problem only if a user does thousands of likes to different events, and in my opinion this will not affect you at the beginning of your development journey :)

In addition, another thing mentioned was that it has more reads than example #1, but this is a fair trade-off as reads are very cheap in Firestore.

#5

likes own collection

A different approach, do not save user likes under the user document (in a sub-collection) and do not save event likes under the event.

Instead, use a new collection, let’s name it ‘eventLikes’ that contains all the likes for events from every user.

When a user likes an event, he should add a document to the eventLikes collection with the data for example: eventId, userId, timestamp.

This way, we can easily set up a trigger function to listen to document creation and deletion and update the event likesCount and simple as that we have a working, fully scalable likes system.

In order to check if a like exists or not (to avoid duplications) we can decide on a document naming convention as ‘eventId_userId’ and then we can easily check if a doc exists in 1 simple read.

If we want to check for likes by a specific user we can simply query the collection with a where statement like so:

firebase.firestore().collection(‘eventLikes’).where(‘userId’,’==’,’XXXX’).get()

Pros

Simple to implement

Fully scalable

Can fetch user likes on a specific number of events (using ‘in’ operator, or just using ‘==’ multiple times) so you don’t have to get all the user likes and save it on client.

Cons

More reads to fetch user likes.

Summary

This solution is fully scalable and very easy to implement!

It will cost a bit more reads for user likes but as I mentioned earlier this is fairly cheap in Firebase.

If you compare it to example #4 where you need a trigger function to listen for user likes that is really annoying because you need to check for new likes and old likes (calculate differences between arrays) and update the event likes. Then a trigger for event likes to update events likesCount, this is too much in comparison to example #5.

In this solution, all you need is two very similar trigger functions — one for doc creations (likes) and one for deletion (unlikes) that updates the event likesCount using a transaction and that’s it (for the backend).

In frontend, its pretty simple too just check if doc exists using the naming convention and to like an event just create a document to eventLikes.

I recommend using Firestore rules to allow user to create or delete docs only if the userId field in the document matches the user uid (request.auth.uid).

Conclusions

There are many considerations needed to be taken care of when choosing a data model for your project.

The data model affects performance, pricing and complexity of development.

There is no RIGHT decision when deciding on a data model for your project.

Every decisions has it’s pros and cons and you should evaluate from which you benefit more for your project needs.

Every data model should be optimized for the project it is made for.

Try to think which actions in your app/project happens the most and try to make the data model optimized for them.

This way you can minimize reads/writes operations and therefore cost :)

I hope you enjoyed reading this article and that I managed to give you some points to look out for and ideas for creating your data model and deciding about your data architecture.

This was my first article, feel free to leave a comment and a clap! ;)

Further Reading

I recommend this post by Francisco García Sierra