Je hebt twee mogelijke manieren waarop een gebruiker een andere gebruiker kan volgen; hetzij direct, hetzij indirect via een groep, in welk geval de gebruiker direct volgt de groep. Laten we beginnen met het opslaan van deze directe relaties tussen gebruikers en groepen:
{
_id: "userA",
followingUsers: [ "userB", "userC" ],
followingGroups: [ "groupX", "groupY" ]
}
Nu wil je in staat zijn om snel ontdek welke gebruikers gebruiker A direct of indirect volgt. Om dit te bereiken, kunt u de groepen denormaliseren die gebruiker A volgt. Laten we zeggen dat groep X en Y als volgt zijn gedefinieerd:
{
_id: "groupX",
members: [ "userC", "userD" ]
},
{
_id: "groupY",
members: [ "userD", "userE" ]
}
Op basis van deze groepen en de directe relaties die gebruiker A heeft, kun je abonnementen . aanmaken tussen gebruikers. Bij elk abonnement worden de herkomst(en) van een abonnement opgeslagen. Voor de voorbeeldgegevens zien de abonnementen er als volgt uit:
// 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" ] }
U kunt deze abonnementen vrij eenvoudig genereren met behulp van een kaart-reduceer-finalize-oproep voor een individuele gebruiker. Als een groep is geüpdatet, hoeft u alleen de kaartverkleining opnieuw uit te voeren voor alle gebruikers die de groep volgen en zijn de abonnementen weer up-to-date.
Kaart verkleinen
De volgende kaartverkleiningsfuncties genereren de abonnementen voor een enkele gebruiker.
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);
}
U kunt vervolgens de kaartverkleining voor een enkele gebruiker uitvoeren door een zoekopdracht op te geven, in dit geval voor userA
.
db.users.mapReduce(map, reduce, { finalize: finalize, query: { _id: "userA" }})
Een paar opmerkingen:
- U moet de eerdere abonnementen van een gebruiker verwijderen voordat u map-reduce voor die gebruiker uitvoert.
- Als je een groep bijwerkt, moet je map-reduce uitvoeren voor alle gebruikers die de groep volgen.
Ik moet er rekening mee houden dat deze functies voor het verkleinen van de kaart complexer bleken te zijn dan ik in gedachten had , omdat MongoDB geen arrays ondersteunt als retourwaarden van reduce-functies. In theorie zouden de functies zou kunnen veel eenvoudiger zijn, maar niet compatibel zijn met MongoDB. Deze complexere oplossing kan echter worden gebruikt om alle users
in kaart te brengen in één keer ophalen, als het ooit moet.