While Dharmaraj's solution will certainly work, I think that there is an even simpler solution for that, that can help you reduce the number of reads a little bit. So let's assume userOne
sends a friend request to userTwo
, and userThree
sends a friend request to the userOne
.
Firestore-root
|
--- users (collection)
| |
| --- $userOneUid (document)
| | |
| | --- //User data
| |
| --- $userTwoUid (document)
| | |
| | --- //User data
| |
| --- $userThreeUid (document)
| |
| --- //User data
|
--- requests (collection)
|
--- $userOneUid (document)
| |
| --- ownRequests (map)
| | |
| | --- $userTwoUid
| | |
| | --- displayName: "User Two"
| | |
| | --- photoUrl: "https://"
| | |
| | --- status: "requested"
| |
| --- friendRequests (map)
| | |
| | --- $userThreeUid
| | |
| | --- displayName: "User Three"
| | |
| | --- photoUrl: "https://"
| | |
| | --- status: "requested"
| |
| --- allFriends (map)
| |
| --- //All friends
|
--- $userTwoUid (document)
| |
| --- friendRequests (map)
| |
| --- $userOneUid
| |
| --- displayName: "User One"
| |
| --- photoUrl: "https://"
| |
| --- status: "requested"
|
--- $userThreeUid (document)
|
--- ownRequests (map)
|
--- $userOneUid
|
--- displayName: "User One"
|
--- photoUrl: "https://"
|
--- status: "requested"
To have a flattened schema, instead of creating nested collections or nested maps under the User object, I have created a separate top-level collection called requests
. Inside this collection, there will be documents that will manage the user's own requests, friend requests, and all friends.
Seeing this schema, you might think, oh, but how can I manage to add such an amount of data in a single document? Even if we're limited to storing up to 1 Mib of storage in a single document, when it comes to storing such simple objects, we can store pretty much. However, if you're worried about the space, then you should consider creating separate collections for each situation, ownRequests
, friendRequests
, and allFriends
. In this case, for simplicity, let's assume all the data fits into a single document.
Going forward, as you can see, we have stored the minimum data from a User object under each map, where the key is the UID and the value is represented by three fields. In this way you can display the name and the profile photo, to clearly indicate the user who performed the friend request.
Now, when userOne
sends a friend request to userTwo
, there are two operations that need to be done. The first one would be to add userOne
inside the friendRequests
map of userTwo
and the second one would be to add userTwo
under its own ownRequests
map.
Between these two friends we have now a friend request, this means that you'll always have to make sure that you'll restrict userTwo
from sending friend requests to userOne
. How can you do that? Simply by checking the existence of the $userTwoUid
inside ownRequests
of userOne
. If you want the userOne
to be able to cancel the friend request, or userTwo
to reject the friend request, then simply revert the above two operations.
The same rule applies in the case userThree
.
Regarding displaying data, if you want to show a list of your own requests, friend requests, or a list of all friends, you need to make a single database call, that costs a single read. If you however need to load more data of a user, then when you click on the user, you can perform another request and load all the data of the user, since you already know the UID.
When userTwo
accepts the friend request from userOne
, simply move their objects inside allFriends
map. You can also go ahead and add a bannerFriends
if you want, to prevent some other users from sending friend requests. Please also note, that this will also cost only a single read.
It seems like a lot of duplicate data so I'm trying to wrap my head around it and find the best and most efficient way to do it in Firestore.
Denormalizing data it's a quite common practice when it comes to NoSQL databases like Firestore and for that I recommend you check my answer from the following post:
Where you'll also be able to see other solutions for structuring such a database.