Cloud Firestore - ensuring data consistency - firebase

My database uses redundant data to speed up fetches and minimise the number of documents that need to be read for certain queries. For example I'd store the names of followed users in a map in a users document so I don't have to read another document to retrieve the names of each of the followed users.
User: (Collection) {
userID: (Document) {
//user state
name: ...
followingUsers: (Map) {
followingUserID: nameOfUser,
followingUserID: nameOfUser
}
}
}
If a user was to change their name, what is the best way to propagate these changes to all places with the redundant data?

Good question!
For starters, I'd recommend doing this kind of administrative task in a server SDK or cloud function, since you don't want a client to necessarily have the ability to start mucking with every single User doc.
The good news is that, once you start using the server SDKs, you can then put a query into a transaction. So let's say user_123 changes their name from "Jenny" to "Jen". Your transaction would look something like this in pseudo-code:
Start Transaction
transaction.get(usersRef.where("followingUsers.user_123", ">=", ""))
Loop through query results. Grab the doc_id from each doc and use that to start building out the writes in your transaction.
transaction.update("/users/<doc_id>/", {"followingUsers.user_123" : "Jen"})
Also make sure you add transactions.update("/users/user_123", {"name": "Jen"})
End transaction
This general approach would also work on the client-side, but you just wouldn't be able to do this in a transaction. (You could still put all of these changes into a batch write, though.)

Related

Best Practice to keep user data in firebase firestore?

I am using firebase as a backend for my Android App. And this app is a social media app where users can post, comment & like. so I am storing user data in user_collection and this collection is secured by security rules where if uid == auth.uid (any user can only access data of himself).
Now in this app when a user post something every user can see this post. And in post_collection I am saving userId in post_doc.
So the problem is I need to show name of user to other users and I have only userId but the problem is a user can't get name of other user by uid beacuse of security rules. now I have to solutions for this please tell me which one is better or you can also suggest any other solutions also?
I can use cloud functions getUserNameById() (Problem : I need to call this function very frequently in feed when user scroll)
I can store name also in post_doc (problem : when user changes his name then It will show old name in old post)
Thanks for you kind help
In a scenario like the one you describe, I would typically store the user name in each post doc. I would also ignore updates to the name, as I think of the user name in the post doc as a historical value: this is the name the user had when they posted this. Now you may want different behavior of course, in which case I recommend reading: How to write denormalized data in Firebase
Your approach with Cloud Functions is fine too, and quite common in some situations. But I tend to only use Cloud Functions for reading data, it the read operation itself is particularly complex, which isn't the case here. In a case like this, I'd recommend coming up with a data model that allows the use-case and security you want.
For example: if you create a collection usernames where each document has the UID as its document ID, and then contains a single field with the username for that UID, you could implement the lookup of the user name through Firestore.
So you could have:
Store the full user profile in /users/$uid.
Store the user name in /usernames/$uid.
Have a Cloud Function that triggers when /users/$uid is written and that updates /usernames/$uid.
The client then has read access to each /usernames/$uid document, or even to the entire /usernames collection in one go if needed.
This way the names can be cached on the client, and continue to work when the app is offline, unlike in your approach with a Cloud Function that looks up the user name.
Consider the solution: whatever public data you need (author name, author userpic link etc) just save it with the post at the time it had created.
So your Message Pojo will looks like:
id
authorName
text
etc..
and just display this name (authorName).
It will be the bad way to go any time to User_collection folder to take the name even if there are would be not strict security (becouse it takes time and document reads)

Firebase Firestore Easy to remember references

We are using Firebase Firestore for data storage. When a user creates a new room, we want the reference to be easy to remember so that a user can share the room ID/code with other users.
At present Firestore will create a unique reference such as:
DvfTMYED5cWdo5qIraZg
This is too long and difficult to remember or share. We could set a different reference manually, but they have to be unique. The other point is that users can create multiple rooms so the reference would have to change each time.
Is there a way to use shorter/better references for this use case?
Firebase/Firestore has nothing built in for shorter references, as they wouldn't have enough entropy to statistically guarantee uniqueness. But since creating chat rooms is likely a fairly low-volume operation, you can implement this in your app by:
Generating your own token for each room, for example a counter.
Checking in the database whether this room is available.
If the token is already taken, generate another one and try again.
This is pretty much how auto-increment fields work on most databases. On Firestore you'd create a document where you keep the current counter value:
chat_rooms (collection)
COUNTERS: { last_room_id: 2 } (document)
chatroom_1: { room_id: 1, name: "Chat room for Stuart and Frank" } (document)
chatroom_2: { room_id: 2, name: "Public chat room" } (document)
When you now create a new room, you:
Start a transaction.
Read COUNTERS.
Read the last_room_id, and increment it.
Write the updated document back.
Create a new document for the new chat room.
Commit the transaction
Note that there are many ways to generate the codes. The counter approach above is a simple one, but I recommend checking out more options. Some interesting reading:
How to generate unique coupon codes?
Generating human-readable/usable, short but unique IDs
Unique Identifiers that are User-Friendly and Hard to Guess

How can you create a transaction/batch write between multiple Firestore instances?

Firebase allows having multiple projects in a single application.
// Initialize another app with a different config
var secondary = firebase.initializeApp(secondaryAppConfig, "secondary");
// Retrieve the database.
var secondaryDatabase = secondary.database();
Example:
Project 1 has my users collection; Project 2 has my friends collection (suppose there's a reason for that). When I add a new friend in the Project 2 database, I want to increment the friendsCount in the user document in Project 1. For this reason, I want to create a transaction/batch write to insure consistency in the data.
How can I achieve this? Can I create a transaction or a batch write between different Firestore instances?
No, you cannot use the database transaction feature across multiple databases.
If absolutely required, I'd probably instead create a custom locking feature. From wiki,
To allow several users to edit a database table at the same time and also prevent inconsistencies created by unrestricted access, a single record can be locked when retrieved for editing or updating. Anyone attempting to retrieve the same record for editing is denied write access because of the lock (although, depending on the implementation, they may be able to view the record without editing it). Once the record is saved or edits are canceled, the lock is released. Records can never be saved so as to overwrite other changes, preserving data integrity.
In database management theory, locking is used to implement isolation among multiple database users. This is the "I" in the acronym ACID.
Source: https://en.wikipedia.org/wiki/Record_locking
It's been three years since the question, I know, but since I needed the same thing I found a working solution to perform the double (or even ^n) transaction. You have to nest the transactions like this.
db1.runTransaction(t1 => db2.runTransaction(t2 => async () => {
await t1.set(.....
await t2.update(.....
etc....
})).then(...).catch(...)
Since the error is propagated in the nested promises it is safe to execute the double transaction in this way because for a failure in any one of the databases it results in the error in all of them.

Custom Authentication in Google Firebase

I have a question regarding authentication using Google Firebase.
For an app, I want to build an authentication similar to the one Slack uses: first, the user provides the input as to which group they want to log in to. If there exists a group with the same name as provided in the input, the user is then taken to a login/signup screen.
I've thought about storing users in the realtime database as follows, but I think there must be a better way to do this (since I don't think I can use the firebase authentication in this case):
groups: {
"some_group_name": {
"users": [
"user1": {
.. user 1 information
},
"user2": {
.. user 2 information
}
],
"group_details": {
"name": ..,
"someGroupDetail": ..
}
},
"some_other_group_name": {
...
}
}
I haven't realized if there is an obvious answer yet, so I'm open to suggestions. How would you suggest I tackle this?
Thanks
PS: I'm building the application using Nativescript and Angular, and (so far) there is no server or database involved other than Firebase.
Another suggestion that might work, is by using Firebase Auth Custom Claims. That way, you only need to store the group ID and group name in your realtime database, without worrying to keep changing the database each time user is added or removed.
This is one way you can do it:
Store database exactly like you have it, with it's group ID and name.
In your backend script (I recommend Cloud Function), each time a User is registering themselves, add custom claims in your user: Specifying what group is the User belong to.
Every time user authenticate, retrieve the group ID from custom claims. And there you get it!
Note: be careful not to put too much information in your custom claims as it cannot exceed 1000 bytes.
Read more about it here: https://firebase.google.com/docs/auth/admin/custom-claims
I would suggest you to implement Root-level collections.
Which is to create collections at the root level of your database to organize disparate data sets(as shown in the image below).
Advantages: As your lists grow, the size of the parent document doesn't change. You also get full query capabilities on
subcollections.
Possible use case: In the same chat app, for example, you
might create collections of users or messages within chat room
documents
Based on the reference from the firebase cloud firestore
Choose a data structure tutorial (I know you are using Realtime database but structuring the database is the same since both are using the NoSQL Schema)
For your case:
Make 2 Collections: Users, Groups
Users: User info is stored in the form of document
Groups: In the Groups Collection, here comes the tricky part, you can either store all groups subcollection under 1 document or split into multiple documents (based on your preference)
In the group-subcollection, you can now store your group info as well as the user assigned where you can store user assigned in the form of array, therefore whenever a user access the group, query the user assigned first, if yes, then allow (assuming users can view all group)
You do the thinking now

Cloud Firestore and data modeling: From RDBMS to No-SQL

I am building an iOS app that is using Cloud Firestore (not Firebase realtime database) as a backend/database.
Google is trying to push new projects towards Cloud Firestore, and to be honest, developers with new projects should opt-in for Firestore (better querying, easier to scale, etc..).
My issue is the same that any relational database developer has when switching to a no-SQL database: data modeling
I have a very simple scenario, that I will first explain how I would configure it using MySQL:
I want to show a list of posts in a table view, and when the user clicks on one post to expand and show more details for that post (let say the user who wrote it). Sounds easy.
In a relational database world, I would create 2 tables: one named "posts" and one named "users". Inside the "posts" table I would have a foreign key indicating the user. Problem solved.
Poor Barry, never had the time to write a post :(
Using this approach, I can easily achieve what I described, and also, if a user updates his/her details, you will only have to change it in one place and you are done.
Lets now switch to Firestore. I like to think of RDBMS's table names as Firestore's collections and the content/structure of the table as the documents.
In my mind i have 2 possible solutions:
Solution 1:
Follow the same logic as the RDBMS: inside the posts collection, each document should have a key named "userId" and the value should be the documentId of that user. Then by fetching the posts you will know the user. Querying the database a second time will fetch all user related details.
Solution 2:
Data duplication: Each post should have a map (nested object) with a key named "user" and containing any user values you want. By doing this the user data will be attached to every post it writes.
Coming from the normalization realm of RDBMS this sounds scary, but a lot of no-SQL documents encourage duplication(?).
Is this a valid approach?
What happens when a user needs to update his/her email address? How easily you make sure that the email is updated in all places?
The only benefit I see in the second solution is that you can fetch both post and user data in one call.
Is there any other solution for this simple yet very common scenario?
ps: go easy on me, first time no-sql dev.
Thanks in advance.
Use solution 1. Guidance on nesting vs not nesting will depend on the N-to-M relationship of those entities (for example, is it 1 to many, many to many?).
If you believe you will never access an entity without accessing its 'parent', nesting may be appropriate. In firestore (or document-based noSQL databases), you should make the decision whether to nest that entity directly in the document vs in a subcollection based on the expect size of that nested entity. For example, messages in a chat should be a subcollection, as they may in total exceed the maximum document size.
Mongo, a leading noSQL db, provides some guides here
Firestore also provided docs
Hope this helps
#christostsang I would suggest a combination of option 1 and option 2. I like to duplicate data for the view layer and reference the user_id as you suggested.
For example, you will usually show a post and the created_by or author_name with the post. Rather than having to pay additional money and cycles for the user query, you could store both the user_id and the user_name in the document.
A model you could use would be an object/map in firestore here is an example model for you to consider
posts = {
id: xxx,
title: xxx,
body: xxx,
likes: 4,
user: {refId: xxx123, name: "John Doe"}
}
users = {
id: xxx,
name: xxx,
email: xxx,
}
Now when you retrieve the posts document(s) you also have the user/author name included. This would make it easy on a postList page where you might show posts from many different users/authors without needed to query each user to retrieve their name. Now when a user clicks on a post, and you want to show additional user/author information like their email you can perform the query for that one user on the postView page. FYI - you will need to consider changes that user(s) make to their name and if you will update all posts to reflect the name change.

Resources