Firebase非规范化数据一致性问题

时间:2017-12-09 19:39:29

标签: firebase firebase-realtime-database ionic2 ionic3 angularfire2

我目前正在使用带有Cordova CLI 7.1.0的Ionic CLI 3.19(@ ionic-app-script 3.1.4)

我目前面临的问题是,每当相关数据从其他地方发生变化时,我应该同时更新朋友节点值。我想用一些截图来澄清我的目标,以使其更清晰。

从下图中可以看出,每个子节点都包含一个用户数组,该用户数组的用户ID为friends节点的键。我作为一个数组存储的原因是因为每个用户可能有很多朋友。 在这个例子中,Jeff Kim有一个朋友,John Doe反之亦然。

friends node image

当用户节点中的数据由于某种原因发生变化时,我希望在friends节点中的相关数据也希望它们也被更新。

例如,当Jeff Kim更改了他的个人资料照片或statusMessage时,所有与朋友节点相同的uid都需要根据用户更改的内容进行更新。

users node image

用户-service.ts

    constructor(private afAuth: AngularFireAuth, private afDB: AngularFireDatabase,){
      this.afAuth.authState.do(user => {
      this.authState = user;
        if (user) {
          this.updateOnConnect();
          this.updateOnDisconnect();
        }
      }).subscribe();
     }

    sendFriendRequest(recipient: string, sender: User) {
      let senderInfo = {
      uid: sender.uid,
      displayName: sender.displayName,
      photoURL: sender.photoURL,
      statusMessage: sender.statusMessage,
      currentActiveStatus: sender.currentActiveStatus,
      username: sender.username,
      email: sender.email,
      timestamp: Date.now(),
      message: 'wants to be friend with you.'
    }
    return new Promise((resolve, reject) => {
      this.afDB.list(`friend-requests/${recipient}`).push(senderInfo).then(() => {
      resolve({'status': true, 'message': 'Friend request has sent.'});
     }, error => reject({'status': false, 'message': error}));
  });
}

    fetchFriendRequest() {
    return this.afDB.list(`friend-requests/${this.currentUserId}`).valueChanges();
  }

    acceptFriendRequest(sender: User, user: User) {
      let acceptedUserInfo = {
      uid: sender.uid,
      displayName: sender.displayName,
      photoURL: sender.photoURL,
      statusMessage: sender.statusMessage,
      currentActiveStatus: sender.currentActiveStatus,
      username: sender.username,
      email: sender.email
     }
     this.afDB.list(`friends/${sender.uid}`).push(user); 
     this.afDB.list(`friends/${this.currentUserId}`).push(acceptedUserI
     this.removeCompletedFriendRequest(sender.uid);
}

根据我刚刚观看过的clip,看起来我做了一个名为Denormalization的事情,解决方案可能正在使用Multi-path updates来更改数据的一致性。 Data consistency with Multi-path updates。但是,完全理解并开始编写一些代码有点棘手。

我已经做过某种练习,以确保在多个位置更新数据,而无需两次调用.update方法。

// I have changed updateUsername method from the code A to code B
// Code A
updateUsername(username: string) {
  let data = {};
  data[username] = this.currentUserId;
  this.afDB.object(`users/${this.currentUserId}`).update({'username': username});
  this.afDB.object(`usernames`).update(data);
}
// Code B
updateUsername(username: string) {
  const ref = firebase.database().ref(); 
  let updateUsername = {};
  updateUsername[`usernames/${username}`] = this.currentUserId; 
  updateUsername[`users/${this.currentUserId}/username`] = username;
  ref.update(updateUsername);
}

我并不是说这是一个完美的代码。但是我已经尝试自己解决这个问题了,而且到目前为止我已经完成了这件事。

假设我目前以杰夫身份登录。

当我运行此代码时,所有与朋友节点中的Jeff关联的数据都会发生变化,并且用户节点中的Jeff数据会同时更新。

代码需要由其他firebase专家进行改进,并且还应该在真实的测试代码上进行测试。

根据以下threadonce('value'(一般来说,使用Firebase获得最佳性能的好主意)。 我应该找出为什么这很糟糕。

friend.ts

    getFriendList() {
      const subscription = this.userService.getMyFriendList().subscribe((users: any) => {
        users.map(u => {
          this.userService.testMultiPathStatusMessageUpdate({uid: u.uid, statusMessage: 'Learning Firebase:)'});
      });
      this.friends = users;
      console.log("FRIEND LIST@", users);
    });
    this.subscription.add(subscription);
  }

用户-service.ts

    testMultiPathStatusMessageUpdate({uid, statusMessage}) {
      if (uid === null || uid === undefined) 
      return;

      const rootRef = firebase.database().ref();
      const query = rootRef.child(`friends/${uid}`).orderByChild('uid').equalTo(this.currentUserId);

    return query.once('value').then(snapshot => {
      let key = Object.keys(snapshot.val());
      let updates = {};
      console.log("key:", key);
      key.forEach(key => {
        console.log("checking..", key);
        updates[`friends/${uid}/${key}/statusMessage`] = statusMessage;
      });
      updates[`users/${this.currentUserId}/statusMessage`] = statusMessage;
      return rootRef.update(updates);
    });
  }

以下代码在将状态更新为在线但未脱机时正常工作。

我认为这不是正确的方法。

    updateOnConnect() {
      return this.afDB.object('.info/connected').valueChanges()
             .do(connected => {
             let status = connected ? 'online' : 'offline'
             this.updateCurrentUserActiveStatusTo(status)
             this.testMultiPathStatusUpdate(status)
             })
             .subscribe()
    }


    updateOnDisconnect() {
      firebase.database().ref().child(`users/${this.currentUserId}`)
              .onDisconnect()
              .update({currentActiveStatus: 'offline'});
      this.testMultiPathStatusUpdate('offline');
    }


    private statusUpdate(uid, status) {
      if (uid === null || uid === undefined) 
      return;

      let rootRef = firebase.database().ref();
      let query = rootRef.child(`friends/${uid}`).orderByChild('uid').equalTo(this.currentUserId);

      return query.once('value').then(snapshot => {
        let key = Object.keys(snapshot.val());
        let updates = {};
        key.forEach(key => {
          console.log("checking..", key);
          console.log("STATUS:", status);
          updates[`friends/${uid}/${key}/currentActiveStatus`] = status;
      });
      return rootRef.update(updates);
    });
  }

    testMultiPathStatusUpdate(status: string) {
      this.afDB.list(`friends/${this.currentUserId}`).valueChanges()
      .subscribe((users: any) => {
        users.map(u => {
          console.log("service U", u.uid);
          this.statusUpdate(u.uid, status);
        })
      })
    }

enter image description here

它确实在控制台中显示offline,但更改未显示在Firebase数据库中。

有没有人可以帮助我? :(

1 个答案:

答案 0 :(得分:2)

我认为你正确地做了这种非规范化,你的多路径更新是正确的方向。但假设有几个用户可以有几个朋友,我会错过朋友们的循环。表。

您应该拥有表格usersfriendsuserFriend。最后一个表就像是在friends中找到用户的快捷方式,在这个表格中你需要迭代每个朋友以找到需要更新的用户。

我在first_app_example [angular 4 + firebase]中采用了不同的方法。我从客户端删除了该进程,并通过Cloud函数中的onUpdate()将其添加到服务器中。

code bellow用户更改其名称时,云功能会在用户已编写的每个评论中执行并更新名称。在我的情况下,客户端不知道非规范化。

//Executed when user.name changes
exports.changeUserNameEvent = functions.database.ref('/users/{userID}/name').onUpdate(event =>{
    let eventSnapshot = event.data;
    let userID = event.params.userID;
    let newValue = eventSnapshot.val();

    let previousValue = eventSnapshot.previous.exists() ? eventSnapshot.previous.val() : '';

    console.log(`[changeUserNameEvent] ${userID} |from: ${previousValue} to: ${newValue}`);

    let userReviews = eventSnapshot.ref.root.child(`/users/${userID}/reviews/`);
    let updateTask = userReviews.once('value', snap => {
    let reviewIDs = Object.keys(snap.val());

    let updates = {};
    reviewIDs.forEach(key => { // <---- note that I loop in review. You should loop in your userFriend table
        updates[`/reviews/${key}/ownerName`] = newValue;
    });

    return eventSnapshot.ref.root.update(updates);
    });

    return updateTask;
});

修改

  

问:我是否正确构建了朋友节点

我更喜欢仅复制(非规范化)我需要的信息。遵循这个想法,你应该复制&#39; userName&#39;和&#39; photoURL&#39;例如。你可以远离所有的朋友。信息分两步:

 let friends: string[];
 for each friend in usrService.getFriend(userID)
    friends.push(usrService.getUser(friend))
  问:你的意思是我应该创建一个Lookup表吗?

你问题中提到的clip,David East给了我们一个如何反规范化的例子。原来他有usersevents。在非规范化中,他创建了一个类似于vlookup的eventAttendees(就像你很伤心)。

  问:你能举个例子吗?

不确定。我删除了一些用户的信息,并添加了一个额外的字段friendshipTypes

users
    xxsxaxacdadID1
        currentActiveStatus: online
        email: zinzzkak@gmail.com
        gender: Male
        displayName: Jeff Kim
        photoURL: https://firebase....
        ...
    trteretteteeID2
        currentActiveStatus: online
        email: hahehahaheha@gmail.com
        gender: Male
        displayName: Joeh Doe
        photoURL: https://firebase....
        ...

friends
    xxsxaxacdadID1
        trteretteteeID2
            friendshipTypes: bestFriend //<--- extra information
            displayName: Jeff Kim
            photoURL: https://firebase....
    trteretteteeID2
        xxsxaxacdadID1
            friendshipTypes: justAfriend //<--- extra information
            displayName: John Doe
            photoURL: https://firebase....


userfriends
    xxsxaxacdadID1
        trteretteteeID2: true
        hgjkhgkhgjhgID3: true
    trteretteteeID2
        trteretteteeID2: true