Ответ 1
У вас есть два возможных способа, которыми пользователь может следовать за другим пользователем; либо прямо, либо косвенно через группу, и в этом случае пользователь непосредственно следует за группой. Начнем с сохранения этих прямых отношений между пользователями и группами:
{
_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 = 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);
}
Затем вы можете запустить сокращение карты для одного пользователя, указав запрос, в этом случае для userA
.
db.users.mapReduce(map, reduce, { finalize: finalize, query: { _id: "userA" }})
Несколько примечаний:
- Вы должны удалить предыдущие подписки пользователя, прежде чем запускать снимок карты для этого пользователя.
- Если вы обновляете группу, вы должны запустить снимок карты для всех пользователей, следующих за группой.
Следует отметить, что эти функции уменьшения отображения оказались более сложными, чем то, что я имел в виду, поскольку MongoDB не поддерживает массивы как возвращаемые значения функций сокращения. Теоретически функции могут быть намного проще, но не совместимы с MongoDB. Однако это более сложное решение может быть использовано для преобразования всей коллекции users
в один вызов, если вам когда-либо понадобится.