FrankTheTank
FrankTheTank

Reputation: 785

Firestore: How to Store Data on User and Global Levels with Calculations?

I was wondering what the best way to store data in firestore on a user and global level is so that all of the information is easily retrievable and can be calculated.

For instance, let's say you are making a race car app where your average time around the track is recorded to a specific user but you also want to use this user's average time to compare against the global average in that user's location.

A setup I am thinking of is like this:

Global Firestore Data

{
  Location: France,
  globalAverageTime: 6 minutes,
  numberOfParticipants: 2
}

User Firestore Data

[
 {
   username: "firstuser",
   location: France
   time: 8 minutes
 },
 {
  username: "seconduser",
  location: France
  time: 4 minutes
 }
]

So essentially, what is the best way to house all of this type of data? So that every time a user updates their personal average time around the track a new global average is calculated based on that user's location and a new time. So for instance in the above example, there are two "number of participants" in France one with 8 minutes and one with 4 minutes so the average time is 6 minutes around the track for that region.

To run a system like this would the best thing to do be use cloud functions that run a calculation after every update to user data and then update the global data? Also is this the best way for setting up the system so that each user can then later compare themselves to the average?

Let me know any improvements that can be made or how I should change how the data is set up in firestore.

Upvotes: 1

Views: 656

Answers (2)

Alex Mamo
Alex Mamo

Reputation: 138834

IMHO, you can keep your database structure as it is. And to answer your questions:

To run a system like this would the best thing to do be use cloud functions that run a calculation after every update to user data and then update the global data?

The best solution is to use Cloud Function because you can calculate those numbers server side. So you'll be able to automatically run backend code in response to events triggered by Firebase features and HTTPS requests. In your particular case, you should change the globalAverageTime property only if one of the time property within those two user objects changes.

Also is this the best way for setting up the system so that each user can then later compare themselves to the average?

In my opinion, yes because you'll be able to compare client side user's best time with the global time.

Upvotes: 0

Chris
Chris

Reputation: 8020

Interesting question! I think subcollections might be the answer here.

You could attack the problems a few different ways. But the first one that comes to mind is this:

Consider the following top level collections:

  • Users
  • Tracks

The 'users' collection contains the tracks driven by the user like this:

-- Users
   -- user_1
      -- tracks (object)
         -- track_1: true (could also be a float instead of boolean, representing the completion time for the user)
         -- track_2: true
         -- track_5: true

Then in the 'tracks' collection you have the following:

-- Tracks
   -- track_1
      -- location
      -- globalAverageTime
      -- numberOfParticipants

      -> 'users' (subcollection)
          -- user_1 (document)
             -- completionTime: 6.43

That way, you can have you function fire each time a new document is written to the tracks -> users subcollection and update via a transaction.

EDIT To answer your question regarding having many users in the tracks subcollection: If you fetch your /tracks/track_1 collection, it will not fetch the users subcollection. You have to specifically fetch that subcollection to retrieve that data, that means that you can easily retrieve a track, or multiple tracks without also fetching all the users.

You can read about transactions here.

From the docs:

Using the Cloud Firestore client libraries, you can group multiple operations into a single transaction. Transactions are useful when you want to update a field's value based on its current value, or the value of some other field. You could increment a counter by creating a transaction that reads the current value of the counter, increments it, and writes the new value to Cloud Firestore.

So you could have a function watching /tracks/{track_id}/users/{user_id} where each time the users completes the track and sets a time, you update the track with the new average

Upvotes: 2

Related Questions