Имате два възможни начина, по които потребителят може да следва друг потребител; пряко или непряко чрез група, в който случай потребителят директно следва групата. Нека започнем със съхраняването на тези директни отношения между потребители и групи:
{
_id: "userA",
followingUsers: [ "userB", "userC" ],
followingGroups: [ "groupX", "groupY" ]
}
Сега ще искате да можете бързо разберете кои потребители следва потребител А, пряко или непряко. За да постигнете това, можете да денормализирате групите, които потребител А следва. Да кажем, че групите X и Y са дефинирани по следния начин:
{
_id: "groupX",
members: [ "userC", "userD" ]
},
{
_id: "groupY",
members: [ "userD", "userE" ]
}
Въз основа на тези групи и преките връзки, които потребител А има, можете да генерирате абонаменти между потребители. Произходът(ите) на абонамента се съхраняват с всеки абонамент. За примерните данни абонаментите ще изглеждат така:
// 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 = 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 за този потребител.
- Ако актуализирате група, трябва да стартирате map-reduce за всички потребители, които следват групата.
Трябва да отбележа, че тези функции за намаляване на картата се оказаха по-сложни от това, което имах предвид , тъй като MongoDB не поддържа масиви като връщани стойности на функциите за намаляване. На теория функциите могат е много по-просто, но не би било съвместимо с MongoDB. Въпреки това, това по-сложно решение може да се използва за намаляване на картографирането на всички users
събиране с едно обаждане, ако някога се наложи.