1

I am using Firebase Firestore with Swift and SwiftUI. I have two collections Users and Tweets. Each Tweet store the userId of the user who tweeted. I want to fetch all the tweets along with the user information associated with tweet. Since both are stored in different collections at the root level, this poses a problem. Here is one of the solutions.

 private func populateUserInfoIntoTweets(tweets: [Tweet]) {
        
        var results = [Tweet]()
        
        tweets.forEach { tweet in
            var tweetCopy = tweet
            self.db.collection("users").document(tweetCopy.userId).getDocument(as: UserInfo.self) { result in
                switch result {
                    case .success(let userInfo):
                        tweetCopy.userInfo = userInfo
                        results.append(tweetCopy)
                        
                        if results.count == tweets.count {
                            DispatchQueue.main.async {
                                self.tweets = results.sorted(by: { t1, t2 in
                                    t1.dateUpdated > t2.dateUpdated
                                })
                            }
                        }
                        
                    case .failure(let error):
                        print(error.localizedDescription)
                }
            }
        }
    }

Kinda messy!

Another way is to store the documentRef of the User doc instead of the userId.

If I store the User document reference then I am using the following code:

  init() {
        
        db.collection("tweets").order(by: "dateUpdated", descending: true)
            .addSnapshotListener { snapshot, error in
                if let error {
                    print(error.localizedDescription)
                    return
                }
                
                snapshot?.documents.compactMap { document in
                    try? document.data(as: Tweet.self)
                }.forEach { tweet in
                    var tweetCopy = tweet
                    tweetCopy.userDocRef?.getDocument(as: UserInfo.self, completion: { result in
                        switch result {
                            case .success(let userInfo):
                                tweetCopy.userInfo = userInfo
                                self.tweets.append(tweetCopy)
                            case .failure(let error):
                                print(error.localizedDescription)
                        }
                    })
                }
                
            }
    }

The above code does not work as expected. I am wondering there must be a better way. I don't want to store all the user information with the Tweet because it will be storing a lot of extra data.

Recommendations?

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Mary Doe
  • 1,073
  • 1
  • 6
  • 22

1 Answers1

1

The common alternative is to duplicate the necessary information of the user in their tweets, a process known as denormalization. If you come from a background in relational databases this may sounds like blasphemy, but it's actually quite common in NoSQL data modeling (and one of the reasons these databases scale to such massive numbers of readers).

If you want to learn more about such data modeling considerations, I recommend checking out NoSQL data modeling and watching Todd's excellent Get to know Cloud Firestore series.

If you're wondering how to keep the duplicated data up to date, this is probably also a good read: How to write denormalized data in Firebase

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thanks! I wonder how far I this hierarchy can go without having performance implications. Each Tweet can have UserInfo but what about Comments on each Tweet. Can I store all comments associated with each tweet as a sub collection on the tweet? Will that have any performance issues? – Mary Doe Jul 16 '22 at 15:43
  • 1
    Storing comments for a post in a subcollection under that post-document is definitely possible and quite common. There's no way to answer a blanket statement about performance issues like that though, but if you have a specific performance issue I recommend posting about that in a new question. Always remember that Firestore has a (rather unique) performance guarantee that the size of a collection has no influence on the performance of reading a fixed number of results from it. Keeping that in mind while designing your data model, will get you to the best model for your use-cases fastest! – Frank van Puffelen Jul 16 '22 at 16:26
  • 1
    For more on that guarantee around read performance, see https://stackoverflow.com/questions/46603205/queries-scale-with-the-size-of-your-result-set-not-the-size-of-your-data-set/46608084#46608084 – Frank van Puffelen Jul 16 '22 at 16:26