У вас есть два возможных способа, которыми пользователь может следовать за другим пользователем; либо напрямую, либо косвенно через группу, и в этом случае пользователь напрямую следует за группой. Давайте начнем с хранения этих прямых отношений между пользователями и группами:
{
_id: "userA",
followingUsers: [ "userB", "userC" ],
followingGroups: [ "groupX", "groupY" ]
}
Теперь вам нужно будет быстро выяснить, за какими пользователями следует пользователь А, прямо или косвенно. Чтобы достичь этого, вы можете денормализовать группы, за которыми следует пользователь А. Допустим, группы 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-redu-finalize для отдельного пользователя. Если группа обновляется, вам нужно только повторно запустить map-limit для всех пользователей, которые следуют за группой, и подписки снова будут обновлены.
Карта-свертка
Следующие функции уменьшения карты будут генерировать подписки для одного пользователя.
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-reduction для одного пользователя, указав запрос, в данном случае для userA
.
db.users.mapReduce(map, reduce, { finalize: finalize, query: { _id: "userA" }})
Несколько заметок:
- Вы должны удалить предыдущие подписки пользователя, прежде чем запускать map-lower для этого пользователя.
- Если вы обновляете группу, вы должны запустить map-lower для всех пользователей, которые следуют за группой.
Я должен отметить, что эти функции сокращения карты оказались на более сложными, чем я имел в виду , потому что MongoDB не поддерживает массивы как возвращаемые значения функций сокращения. Теоретически, функции могут быть намного проще, но не совместимы с MongoDB. Однако это более сложное решение можно использовать для отображения-сокращения всей коллекции users
за один вызов, если вам когда-либо понадобится.