Should I let users/clients upload data directly to Firestore or through a Callable Cloud Function?

43 views
Skip to first unread message

george outters

unread,
Oct 24, 2022, 12:59:48 AM10/24/22
to Firebase Google Group

Consider the following simple Firestore structure:

db/posts/{postId}/comments/{commentId}

post document contains the fields

---------POST--------- 
 comment_count: 0

comment document contains the fields

--------COMMENT---------
 text: ""

Current solution

When a user posts a comment from the client app, they do so using the Firestore SDK (for Flutter in my case) directly into Firestore.

db.col("posts").doc(postId).col("comments").doc() .set({ comment:"Hello there!" })

I have written Firestore Rules to validate certain field in the new comment doc

match /posts/{postId}/comments/{commentId}{ allow create: if request.resource.data.text.size() < 100 }

When this document gets passed by the rules and added, a cloud function triggers, which increments the comment_count of the post

exports.onCommentCreate = functions.firestore.document("posts/{postId}/comments/{commentId}") .onCreate(async (snap, context) => { db.col("posts").doc(postId) .update({ "comment_count": admin.firestore.FieldValue.increment(1) }); });

When a comment document is removed, another cloud function triggers, which decrements the comment_count of the post

Problem

If any cloud functions fail after a comment document has been added directly by the client, such as the one that increments the comment_count on the post document, the comment doc which was added by the user will remain, even though the comment hasn't been "registered" properly. This means that our database may become unsynced, which is bad.

A fix to this would be catch the functions errors, and delete the comment doc which was added in the same function if something goes wrong. That would sync everything, however, it would cause other problems such as the decrement function now being triggered (as the failing function removed the comment) which would result in comment_count being -1. Also, I have no way of responding to the user with an error

Proposed solution

A proposed solution to this could be to let clients add comments using a Callable Cloud function instead of adding directly to the database. Suppose I have the following function:

exports.addComment = functions.https.onCall(async (data, context) => { 
 //Check if data follows the rules and if user is authenticated 
 //using the isValid() function 
 //ex. if (data.text.length < 100) <-- valid 
 if(!isValid(data, context)) return {info: "Not valid"};
 const batch = db.batch(); 

 batch.set(commentRef,{ 
 text: data.text
 });

 batch.update(postRef,{ 
 "comment_count": admin.firestore.FieldValue.increment(1) 
 }); 

 try { 

 await batch.commit(); 
 return { info: "Success" }; } 

 catch (e) {

 return { info: "Error" }; 

 } });

This solution...

  • Evaluates the incoming request and data with rules (ex. checking auth, data types/values)
  • Checks if the post document's comment_count can be updated (using a batch write).
  • If everything is fine, THEN the comment document and comment_count field will be written at the same time rather than one after the other.
  • Another plus is that we can provide feedback to the user about their action by sending a response.

A similar function would be written for deleting the comment as well, which has the same benefits

Questions

The questions I would like answers to are:

  1. Is using a Callable Cloud function in this way a good idea for a social media designed to scale a bit? Is this less efficient performance or cost wise than my current solution (where a client adds directly to Firestore, using trigger functions for syncing)?
  2. Any other possible drawbacks?
  3. Any other solutions/best practices/thoughts?

Sorry for a long text, I tried my best to simplify and organize things :) Any response would be greatly appreciated!

Reply all
Reply to author
Forward
0 new messages