Modelling a Chat like Application in Firebase

2020-08-16 08:23发布

问题:

I have a Firebase database structuring question. My scenario is close to a chat application. Here are the specifics

- users(node storing several users of the app)
  - id1
      name: John
  - id2
      name: Meg
  - id2
      name: Kelly
- messages(node storing messages between two users)
  - message1
      from: id1
      to: id2
      text: ''
  - message2
      from: id3
      to: id1
      text: ''

Now imagine building a conversations view for an individual user. So I want to fetch all messages from that particular user and to that particular user

I am writing it as follows right now:

let fromMessagesRef = firebase.database().ref('messages').orderByChild('from').equalTo(firebase.auth().currentUser.uid)
fromMessagesRef.once("value").then((snapshot) => {/* do something here*/})

let toMessagesRef = firebase.database().ref('messages').orderByChild('to').equalTo(firebase.auth().currentUser.uid)
toMessagesRef.once("value").then((snapshot) => {/* do something here*/})

Questions:

  1. Is this the right way to model the problem?
  2. If yes, is there a way to combine the above 2 queries?

回答1:

I would store the data like this:

- users(node storing several users of the app)
  - id1
      name: John
      messages
        message1: true
        message2: true
  - id2
      name: Meg
      messages
        message1: true
        message3: true
  - id3
      name: Kelly
      messages
        message2: true
        message3:true
- messages(node storing messages between two users)
  - message1
      from: id1
      to: id2
      text: ''
  - message2
      from: id3
      to: id1
      text: ''
  - message3
      from: id2
      to: id3
      text: ''

Firebase recommends storing things like this. So in your case your query would be

let fromMessagesRef = firebase.database().child('users').child(firebase.auth().currentUser.uid).child('messages')

This allows it to be very fast as there is no orderBy being done. Then you would loop over each message and get it's profile from the messages node.



回答2:

The structure you have is one possible way to model this data. If you're building an application like this, I would highly recommend the angularfire-slack tutorial. One potentially faster way to model the data would be to model the data like is suggested in this tutorial https://thinkster.io/angularfire-slack-tutorial#creating-direct-messages

{
  "userMessages": {
    "user:id1": {
      "user:id2": {
        "messageId1": {
          "from": "simplelogin:1",
          "body": "Hello!",
          "timestamp": Firebase.ServerValue.TIMESTAMP
        },
        "messageId2": {
          "from": "simplelogin:2",
          "body": "Hey!",
          "timestamp": Firebase.ServerValue.TIMESTAMP
        }
      }
    }
  }
}

The one thing you need to watch for in this case if you choose to do it like this is that before your query, you need to sort which user will be the "primary user" under whom the messages will be stored. As long as you make sure that is the same every time, you should be good to go.

One improvement you could make to this structure is something you already pointed out - flattening your data and moving the messages to another node - like you did in your example.

To answer your second question, if you were to keep that structure, I think you would need both of those queries, because firebase does not support a more complicated OR query that would allow you to search both at the same time.



回答3:

No. Firebase Auth subsystem is where you want to store the email, displayName, password, and photoURL for each user. The function below is how you do it for a password-based user. oAuth-based users are easier. If you have other properties you want to store, like age for example, put those under a users node with each users uid that Firebase Authentication provides you.

  function registerPasswordUser(email,displayName,password,photoURL){
    var user = null;
    //NULLIFY EMPTY ARGUMENTS
    for (var i = 0; i < arguments.length; i++) {
      arguments[i] = arguments[i] ? arguments[i] : null;
    }
    auth.createUserWithEmailAndPassword(email, password)
    .then(function () {
      user = auth.currentUser;
      user.sendEmailVerification();
    })
    .then(function () {
      user.updateProfile({
        displayName: displayName,
        photoURL: photoURL
      });
    })
    .catch(function(error) {
      console.log(error.message);
    });
    console.log('Validation link was sent to ' + email + '.');
  }

As for the messages node, get the random id from Firebase Realtime Database's push method and use that as the id of each message under messages. Firebase queries are used:

var messages = firebase.database().ref('messages');
var messages-from-user = messages.orderByChild('from').equalTo('<your users uid>');
var messages-to-user = messages.orderByChild('to').equalTo('<your users uid>');

messages-from-user.once('value', function(snapshot) {
  console.log('A message from <your users uid> does '+(snapshot.exists()?'':'not ')+' exist')
});

messages-to-user.once('value', function(snapshot) {
  console.log('A message to <your users uid> does '+(snapshot.exists()?'':'not ')+' exist')
});

Define an index for messages-from-user and messages-to-user in your Rules:

{
  "rules": {
    "messages": {
      ".indexOn": ["from", "to"] 
    }
  }
}


回答4:

Below data structure gives you more flexibility with you data. Instead of having to store each messages that user sent back and forth I would suggest to store it in separate node and store the messageID with each user that is involved in the conversation.

Obviously you need to set the security rules, so other user can't see conversation if they are not in the conversation.

By doing this we are not creating deep chain node inside user info

   - users(node storing several users of the app)
      - id1
          name: John
          messages: [msID1, msID9]
      - id2
          name: Meg
          messages: [msID1, msID7]
      - id3
          name: Kelly
           messages: [msID9, msID7]

    - messages(node storing messages between two users)
      - msID1
          from: id1
          to: id2
          text: ''
      - msID7
          from: id3
          to: id2
          text: ''
       - msID9
          from: id3 
          to: id1
          text: ''


回答5:

Firebase has actually built a demo (and extendible) chat application called Firechat. The source and documentation is provided, and of particular note is the section on their data structures.

Although they've implemented chatrooms, you can see that they've flattened their data structures as in many of the other answers. You can read more about how and why this is done in the Firebase guide.