我正在创建一个使用经典“跟随”机制的应用程序(Twitter和网络上的许多其他应用程序使用的机制)。我正在使用MongoDB。 不过,我的系统有所不同:用户可以关注用户的群组。这意味着,如果您关注某个群组,您将自动关注该群组成员的所有用户。当然,用户可以属于多个组。
这就是我提出的:
following
)following
数组群组的工作方式相同:当用户A 跟随群组X 时,群组X的ID会添加到following
数组中。 (我实际上添加了DBRef
,所以我知道连接是针对用户还是组。)
当我必须检查用户A 是否跟在组X 之后,我只是在用户A 中搜索群组的ID在数组之后。
$or
条件来检查用户A是直接跟踪用户B还是通过组跟踪。像这样:
db.users.find({'$or':{'following.ref.$id':$user_id,'following.ref.$ref','users'},{'following.ref.$id':{'$in':$group_ids},'following.ref.$ref':'groups'}}})
这很好,但我想我有一些问题。例如,如何显示特定用户的关注者列表,包括分页?我不能在嵌入文档上使用skip()和limit()。
我可以更改设计并使用userfollow
集合,这将对嵌入式following
文档执行相同的工作。我尝试过这种方法的问题是,使用我之前使用的$or
条件,跟随两个包含相同用户的组的用户将被列出两次。为了避免这种情况,我可以使用group或MapReduce,我实际上做了它并且它可以工作,但是我希望避免这样做以保持简单。也许我只需要开箱即用。或者也许我在两次尝试时采取了错误的方法。任何人都必须做类似的事情并提出更好的解决方案?
(这实际上是我this older question的后续行动。我决定发布一个新问题来更好地解释我的新情况;我希望这不是问题。)
答案 0 :(得分:15)
用户可以通过两种方式关注其他用户;直接或间接通过组,在这种情况下,用户直接跟随该组。我们首先在用户和组之间存储这些直接关系:
{
_id: "userA",
followingUsers: [ "userB", "userC" ],
followingGroups: [ "groupX", "groupY" ]
}
现在,您希望能够快速直接或间接地找出用户A正在关注的用户。要实现此目的,您可以对用户A正在关注的组进行非规范化。假设组X和Y定义如下:
{
_id: "groupX",
members: [ "userC", "userD" ]
},
{
_id: "groupY",
members: [ "userD", "userE" ]
}
根据这些群组以及用户A所拥有的直接关系,您可以在用户之间生成订阅。订阅的来源与每个订阅一起存储。对于示例数据,订阅将如下所示:
// 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" ] }
您可以使用针对单个用户的map-reduce-finalize调用轻松生成这些订阅。如果更新了组,则只需为该组后面的所有用户重新运行map-reduce,订阅将再次更新。
以下map-reduce函数将为单个用户生成订阅。
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);
}
然后,您可以通过指定查询来为单个用户运行map-reduce,在本例中为userA
。
db.users.mapReduce(map, reduce, { finalize: finalize, query: { _id: "userA" }})
一些注意事项:
我应该注意到这些map-reduce函数比我想象的更复杂,因为MongoDB不支持数组作为reduce函数的返回值。理论上,函数可以更简单,但与MongoDB不兼容。但是,如果您需要,可以使用这个更复杂的解决方案来映射 - 减少整个users
集合。