Du har två möjliga sätt på vilka en användare kan följa en annan användare; antingen direkt eller indirekt genom en grupp, i vilket fall användaren direkt följer gruppen. Låt oss börja med att lagra dessa direkta relationer mellan användare och grupper:
{
_id: "userA",
followingUsers: [ "userB", "userC" ],
followingGroups: [ "groupX", "groupY" ]
}
Nu vill du kunna snabbt ta reda på vilka användare som användare A följer, antingen direkt eller indirekt. För att uppnå detta kan du avnormalisera grupperna som användare A följer. Låt oss säga att grupp X och Y definieras enligt följande:
{
_id: "groupX",
members: [ "userC", "userD" ]
},
{
_id: "groupY",
members: [ "userD", "userE" ]
}
Baserat på dessa grupper, och de direkta relationer som användare A har, kan du skapa prenumerationer mellan användare. Ursprunget till ett abonnemang lagras med varje abonnemang. För exempeldata skulle prenumerationerna se ut så här:
// 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" ] }
Du kan generera dessa prenumerationer ganska enkelt genom att använda ett map-reduce-finalize-samtal för en enskild användare. Om en grupp uppdateras behöver du bara köra map-reduce igen för alla användare som följer gruppen och prenumerationerna kommer att vara uppdaterade igen.
Karta-minska
Följande map-reduce-funktioner genererar prenumerationerna för en enskild användare.
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);
}
Du kan sedan köra map-reduce för en enskild användare, genom att ange en fråga, i det här fallet för userA
.
db.users.mapReduce(map, reduce, { finalize: finalize, query: { _id: "userA" }})
Några anteckningar:
- Du bör ta bort en användares tidigare prenumerationer innan du kör map-reduce för den användaren.
- Om du uppdaterar en grupp bör du köra map-reduce för alla användare som följer gruppen.
Jag bör notera att dessa kartreducerande funktioner visade sig vara mer komplexa än vad jag hade i åtanke , eftersom MongoDB inte stöder arrayer som returvärden för reduceringsfunktioner. I teorin kunde funktionerna vara mycket enklare, men skulle inte vara kompatibel med MongoDB. Denna mer komplexa lösning kan dock användas för att kartreducera hela users
insamling i ett enda samtal, om du någonsin måste.