使用 Meteor 建模和发布基于关注者的提要

Modeling and publishing a follower-based feed with Meteor

我正在开发一个简单的应用程序,用户可以在其中关注 其他用户。用户可以posts。用户的供稿由他们关注的用户加星标的 post 组成。其实很简单。然而,这一切在 Mongo 和 Meteor 中变得复杂...

我能想到的基本上有两种建模方式:

  1. 一个用户有一个属性,following,是用户关注的userId数组。此外,post 有一个 属性、starrers,这是一个为这个 post 加注星标的用户 ID 数组。这种方法的好处是发布比较简单:

    Meteor.publish 'feed', (limit) ->
      Posts.find({starrers: {$in: Meteor.users.findOne(@userId).following}}, {sort: {date: -1}, limit:limit})
    

    我们不会被动地倾听用户关注的人,但目前还算不错。这种方法的主要问题是 (1) 如果 1000000 人 star a post,单个文档将变得庞大且效率低下。另一个问题是 (2) 跟踪信息会很痛苦,例如用户何时开始关注另一个用户或用户何时为 post.

  2. 加注星标
  3. 另一种方法是再创建两个集合,StarsFollows。如果用户为 post 加注星标,那么我们将创建一个具有属性 userIdpostId 的文档。如果一个用户关注另一个用户,那么我们将创建一个具有属性 userIdfollowId 的文档。这为我们提供了 UsersPosts 的较小文档大小的优势,但在查询时却很复杂,特别是因为 Mongo 不处理连接!

现在,我做了一些研究,人们似乎同意第二种选择是正确的选择。现在我遇到的问题是高效查询和发布。基于 Discover Meteor chapter about Advanced Publications,我创建了一个出版物,用于发布由用户的关注者加注星标的 post——已排序且有限。

# a helper to handle stopping observeChanges
observer = (sub, func) ->
  handle = null
  sub.onStop -> 
    handle?.stop?()
  () ->
    handle?.stop?()
    handle = func()


Meteor.publish 'feed', (limit) ->
  sub = this
  userId = @userId

  followIds = null
  eventIds = null

  publishFollows = observer sub, () ->
    followIds = {}
    Follows.find({userId:userId}).observeChanges 
      added: (id, doc) ->
        followIds[id] = doc.followId
        sub.added('follows', id, doc)
        publishStars()   
      removed: (id) ->
        delete followIds[id]
        sub.removed('follows', id)
        publishStars()

  publishStars = observer sub, () ->
    eventIds = {}
    Stars.find({userId: {$in: _.keys(followIds)}).observeChanges 
      added: (id, doc) ->
        eventIds[id] = null
        sub.added('stars', id, doc)
        publishEvents()
      removed: (id) ->
        delete eventIds[id]
        sub.removed('stars', id)
        publishEvents()

  publishEvents = observer sub, () ->
    Events.find({_id: {$in: _.keys(eventIds)}}, {sort: {name:1, date:-1}, limit:limit}).observeChanges 
      added: (id, doc) ->
        sub.added('events', id, doc)
      changed: (id, fields) ->
        sub.changed('events', id, fields)
      removed: (id) ->
        sub.removed('events', id)

虽然这可行,但规模似乎非常有限。特别是,我们必须编制一份由每个关注者加注星标 post 的列表。此列表的大小将增长得非常快。然后我们对所有 post 进行一个巨大的 $in 查询。

另一个烦恼是在我们订阅后在客户端查询提要:

Meteor.subscribe("feed", 20)
posts = null
Tracker.autorun ->
  followers = _.pluck(Follows.find({userId: Meteor.userId()}).fetch(), "followId")
  starredPostIds = _.pluck(Stars.find({userId: {$in: followers}}).fetch(), "postId")
  posts = Posts.find({_id: {$in: starredPostIds}}, {sort: {date: -1}, limit: 20}).fetch()

这就像我们把所有这些工作都做了两次。首先,我们在服务器上完成所有工作以发布提要。然后我们需要在客户端再次通过完全相同的逻辑来获取那些 posts...

我这里的问题是关于一切的设计问题。 如何根据关注者注视 post 来高效设计此 Feed? 我应该使用什么收集/收集模式?我应该如何创建适当的出版物?我如何在客户端查询提要?

所以事实证明 Mongo 和 "non-relational" 数据库根本不是为关系数据设计的。因此,这里没有 Mongo 的解决方案。我最终使用了 Neo4j,但 SQL 也可以正常工作。

meteor add reywood:publish-composite

 Meteor.publishComposite('tweets', function(username) {  
  return {
    find: function() {
      // Find the current user's following users
      return Relationships.find({ follower: username });
    },
    children: [{
      find: function(relationship) {
        // Find tweets from followed users
        return Tweets.find({user: relationship.following});
      }
    }]
  }
});

Meteor.publish('ownTweets', function(username) {  
  return Tweets.find({user: username});
});