类似Twitter的应用程序使用MongoDB [英] Twitter-like app using MongoDB

查看:152
本文介绍了类似Twitter的应用程序使用MongoDB的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在制作一个应用程序,它使用经典的跟随机制(Twitter使用的)和网络上的许多其他应用程序。我正在使用MongoDB。
我的系统有所不同,但用户可以按照使用用户。这意味着,如果您遵循一个群组,您将自动跟踪该群组成员的所有用户。当然用户可以属于多个组。



这是我想出的:




  • 用户A 遵循用户B ,用户B的ID被添加到嵌入式数组(称为 / code>)在用户A的文档

  • 中取消关注,我从以下数组中删除跟随用户的ID

  • 组的工作方式相同:当用户A 遵循组X ,组X的ID被添加到以下数组。 (我实际上添加了一个 DBRef ,所以我知道连接是用户还是组。)


  • p>当我必须检查用户A 是否遵循组X ,我只是在用户A 的以下数组中搜索组的ID。


  • 当我必须检查用户A 是否遵循用户B ,事情会变得更棘手。每个用户的文档都有一个嵌入式数组,列出用户所属的所有组。因此,我使用 $或条件来检查用户A是直接或通过组跟随用户B.像这样:


    db.users.find({'$ or':{'following.ref。$ id':$ user_id,'following.ref。$ ref','users'},{'following.ref。$ id':{'$ in':$ group_ids},'following.ref。$ ref' group'}}})



这工作正常,但我想我有几个问题。例如,如何显示特定用户的关注者列表,包括分页?我不能在嵌入式文档上使用skip()和limit()。


我可以更改设计并使用 userfollow 集合,它将执行嵌入式跟随文档的工作。我尝试的这种方法的问题是,在我之前使用的 $或条件下,包含相同用户的两个组之后的用户将被列出两次。为了避免这种情况,我可以使用组或MapReduce,我实际上是这样做的,但是我很乐意避免这样做,使事情更简单。也许我只需要开箱即用。或者也许我尝试错误的方法。任何人都必须做类似的事情,并提出一个更好的解决方案?



(这实际上是

您有两种可能的方式,用户可以跟随另一位用户;直接或间接地通过组,在这种情况下,用户直接跟随该组。我们开始在用户和组之间存储这些直接关系:

  {
_id: userA,
followingUsers:[userB,userC],
followingGroups:[groupX,groupY]
}

现在,您可以快速快速查找用户A正在跟踪的用户,直接或间接。为了实现这一点,您可以对用户A所关注的组进行非规范化。假设组X和Y定义如下:

  {
_id:groupX,
成员:[userC,userD]
},
{
_id:groupY,
成员:[userD,userE]
$

根据这些组和用户A的直接关系,您可以生成订阅之间的用户。订阅的来源与每个订阅一起存储。对于示例数据,订阅将如下所示:

  //滥用感叹号来表示直接关系
{ ownerId:userA,userId:userB,origin:[! ]},
{ownerId:userA,userId:userC,origin:[!,groupX]},
{ownerId:userA,userId:userD来源:[groupX,groupY]},
{ownerId:userA,userId:userE,origin:[groupY]}
/ pre>

您可以轻松地为单个用户使用map-reduce-finalize调用生成这些订阅。如果一个组被更新,您只需为所有跟随该组的用户重新运行map-reduce,并且订阅将被更新。



< h3> Map-reduce

以下map-reduce功能将为单个用户生成订阅。

  map = function(){
ownerId = this._id;

this.followingUsers.forEach(function(userId){
emit({ownerId:ownerId,userId:userId},{originins:[!]});
});

this.followingGroups.forEach(function(groupId){
group = db.groups.findOne({_id:groupId});

group.members。 forEach(function(userId){
emit({ownerId:ownerId,userId:userId},{originins:[group._id]});
});
});
}

reduce = function(key,values){
origins = [];

values.forEach(function(value){
origins = originins.concat(value.origins);
});

return {originins:origins};
}

finalize = function(key,value){
db.subscriptions.update(key,{$ set:{origins:value.origins}},true);
}

然后,您可以为单个用户运行map-reduce,方法是指定查询,在这种情况下为 userA

  db.users.mapReduce (map,reduce,{finalize:finalize,query:{_id:userA}})

几个注释:




  • 在为该用户运行map-reduce之前,您应该删除用户的以前订阅。

  • 如果您更新一个组,则应为该组后面的所有用户运行map-reduce。



我应该注意,这些map-reduce函数原来比我想到的更复杂,因为MongoDB不支持数组作为reduce函数的返回值。在理论上,函数可以简单得多,但与MongoDB不兼容。但是,如果您有必要,这个更复杂的解决方案可用于映射 - 减少单个调用中的整个用户集合。


I'm making an app that uses the classic "follow" mechanism (the one used by Twitter and a lot of other apps around the web). I'm using MongoDB. My system has a difference, though: an user can follow groups of users. That means that if you follow a group, you'll automatically follow all the users who are members of that group. Of course users can belong to more than one group.

This is what I came up with:

  • when user A follows user B, id of user B gets added to an embedded array (called following) in user A's document
  • for unfollowing, I remove the id of the followed user from the following array
  • groups work in the same way: when user A follows group X, id of group X gets added to the following array. (I actually add a DBRef so I know if the connection is to an user or a group.)

  • when I have to check if user A follows group X, I just search for the group's id in user A's following array.

  • when I have to check if user A follows user B, things gets a little trickier. Each user's document has an embedded array listing all the groups the user belongs to. So I use an $or condition to check if user A is either following user B directly or via a group. Like this:

    db.users.find({'$or':{'following.ref.$id':$user_id,'following.ref.$ref','users'},{'following.ref.$id':{'$in':$group_ids},'following.ref.$ref':'groups'}}})

This works fine, but I think I have a few issues. For example how do I show a list of followers for a particular user, including pagination? I can't use skip() and limit() on an embedded document.

I could change the design and use an userfollow collection, which would do the same job of the embedded following document. The problem with this approach, which I tried, is that with the $or condition I used earlier, users following two groups containing the same user would be listed twice. To avoid this I could use group or MapReduce, which I actually did and it works, but I'd love to avoid this to keep things simpler. Maybe I just need to think out of the box. Or maybe I took the wrong approach with both tries. Anyone already had to do a similar thing and came up with a better solution?

(This is actually a follow-up to this older question of mine. I decided to post a new question to explain my new situation better; I hope it's not a problem.)

解决方案

You have two possible ways in which a user can follow another user; either directly, or indirectly through a group, in which case the user directly follows the group. Let's begin with storing these direct relations between users and groups:

{
  _id: "userA",
  followingUsers: [ "userB", "userC" ],
  followingGroups: [ "groupX", "groupY" ]
}

Now, you'll want to be able to quickly find out which users user A is following, either directly or indirectly. To achieve this, you can denormalize the groups that user A is following. Let's say that group X and Y are defined as follows:

{
  _id: "groupX",
  members: [ "userC", "userD" ]
},
{
  _id: "groupY",
  members: [ "userD", "userE" ]
}

Based on these groups, and the direct relations user A has, you can generate subscriptions between users. The origin(s) of a subscription are stored with each subscription. For the example data the subscriptions would look like this:

// abusing exclamation mark to indicate a direct relation
{ ownerId: "userA", userId: "userB", origins: [ "!" ] },
{ ownerId: "userA", userId: "userC", origins: [ "!", "groupX" ] },
{ ownerId: "userA", userId: "userD", origins: [ "groupX", "groupY" ] },
{ ownerId: "userA", userId: "userE", origins: [ "groupY" ] }

You can generate these subscriptions pretty easily, using a map-reduce-finalize call for an individual user. If a group is updated, you only have to re-run the map-reduce for all users that are following the group and the subscriptions will be up-to-date again.

Map-reduce

The following map-reduce functions will generate the subscriptions for a single user.

map = function () {
  ownerId = this._id;

  this.followingUsers.forEach(function (userId) {
    emit({ ownerId: ownerId, userId: userId } , { origins: [ "!" ] });
  });

  this.followingGroups.forEach(function (groupId) {
    group = db.groups.findOne({ _id: groupId });

    group.members.forEach(function (userId) {
      emit({ ownerId: ownerId, userId: userId } , { origins: [ group._id ] });
    });
  });
}

reduce = function (key, values) {
  origins = [];

  values.forEach(function (value) {
    origins = origins.concat(value.origins);
  });

  return { origins: origins };
}

finalize = function (key, value) {
  db.subscriptions.update(key, { $set: { origins: value.origins }}, true);
}

You can then run the map-reduce for a single user, by specifying a query, in this case for userA.

db.users.mapReduce(map, reduce, { finalize: finalize, query: { _id: "userA" }})

A few notes:

  • You should delete the previous subscriptions of a user, before running map-reduce for that user.
  • If you update a group, you should run map-reduce for all the users that follow the group.

I should note that these map-reduce functions turned out to be more complex than what I had in mind, because MongoDB doesn't support arrays as return values of reduce functions. In theory, the functions could be much simpler, but wouldn't be compatible with MongoDB. However, this more complex solution can be used to map-reduce the entire users collection in a single call, if you ever have to.

这篇关于类似Twitter的应用程序使用MongoDB的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆