Du har to mulige måder, hvorpå en bruger kan følge en anden bruger; enten direkte eller indirekte gennem en gruppe, i hvilket tilfælde brugeren direkte følger gruppen. Lad os begynde med at gemme disse direkte relationer mellem brugere og grupper:
{
_id: "userA",
followingUsers: [ "userB", "userC" ],
followingGroups: [ "groupX", "groupY" ]
}
Nu vil du gerne være i stand til hurtigt finde ud af, hvilke brugere bruger A følger, enten direkte eller indirekte. For at opnå dette kan du denormalisere de grupper, som bruger A følger. Lad os sige, at gruppe X og Y er defineret som følger:
{
_id: "groupX",
members: [ "userC", "userD" ]
},
{
_id: "groupY",
members: [ "userD", "userE" ]
}
Baseret på disse grupper, og de direkte relationer bruger A har, kan du generere abonnementer mellem brugere. Oprindelsen af et abonnement gemmes sammen med hvert abonnement. For eksempeldataene vil abonnementerne se sådan ud:
// 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 generere disse abonnementer ret nemt ved at bruge et kort-reducer-afslut-opkald for en individuel bruger. Hvis en gruppe opdateres, skal du kun køre kort-reducering igen for alle brugere, der følger gruppen, og abonnementerne vil være opdaterede igen.
Kort-reducer
Følgende kort-reducer-funktioner genererer abonnementerne for en enkelt bruger.
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 derefter køre map-reduce for en enkelt bruger ved at angive en forespørgsel, i dette tilfælde for userA
.
db.users.mapReduce(map, reduce, { finalize: finalize, query: { _id: "userA" }})
Et par bemærkninger:
- Du bør slette en brugers tidligere abonnementer, før du kører kort-reducering for denne bruger.
- Hvis du opdaterer en gruppe, bør du køre map-reduce for alle de brugere, der følger gruppen.
Jeg bør bemærke, at disse kort-reducerende funktioner viste sig at være mere komplekse end hvad jeg havde i tankerne , fordi MongoDB ikke understøtter arrays som returværdier for reducere-funktioner. I teorien kunne funktionerne være meget enklere, men ville ikke være kompatibel med MongoDB. Denne mere komplekse løsning kan dog bruges til at kortreducere hele users
indsamling i et enkelt opkald, hvis du nogensinde bliver nødt til det.