Sie haben zwei Möglichkeiten, wie ein Benutzer einem anderen Benutzer folgen kann; entweder direkt oder indirekt über eine Gruppe, in diesem Fall der Benutzer direkt folgt der Gruppe. Beginnen wir damit, diese direkt zu speichern Beziehungen zwischen Benutzern und Gruppen:
{
_id: "userA",
followingUsers: [ "userB", "userC" ],
followingGroups: [ "groupX", "groupY" ]
}
Jetzt möchten Sie in der Lage sein, schnell Finden Sie heraus, welchen Benutzern Benutzer A direkt oder indirekt folgt. Um dies zu erreichen, können Sie die Gruppen denormalisieren, denen Benutzer A folgt. Nehmen wir an, die Gruppen X und Y sind wie folgt definiert:
{
_id: "groupX",
members: [ "userC", "userD" ]
},
{
_id: "groupY",
members: [ "userD", "userE" ]
}
Basierend auf diesen Gruppen und den direkten Beziehungen, die Benutzer A hat, können Sie Abonnements generieren zwischen Benutzern. Die Herkunft(en) eines Abonnements werden mit jedem Abonnement gespeichert. Für die Beispieldaten würden die Abonnements wie folgt aussehen:
// 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" ] }
Sie können diese Abonnements ziemlich einfach generieren, indem Sie einen Map-Reduce-Finalize-Aufruf für einen einzelnen Benutzer verwenden. Wenn eine Gruppe aktualisiert wird, müssen Sie nur die Kartenreduzierung für alle Benutzer erneut ausführen, die der Gruppe folgen, und die Abonnements sind wieder auf dem neuesten Stand.
Karte verkleinern
Die folgenden Map-Reduce-Funktionen generieren die Abonnements für einen einzelnen Benutzer.
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);
}
Sie können dann die Zuordnungsreduzierung für einen einzelnen Benutzer ausführen, indem Sie eine Abfrage angeben, in diesem Fall für userA
.
db.users.mapReduce(map, reduce, { finalize: finalize, query: { _id: "userA" }})
Ein paar Anmerkungen:
- Sie sollten die vorherigen Abonnements eines Benutzers löschen, bevor Sie map-reduce für diesen Benutzer ausführen.
- Wenn Sie eine Gruppe aktualisieren, sollten Sie map-reduce für alle Benutzer ausführen, die der Gruppe folgen.
Ich sollte anmerken, dass sich diese Map-Reduce-Funktionen als komplexer herausstellten, als ich mir vorgestellt hatte , da MongoDB keine Arrays als Rückgabewerte von Reduce-Funktionen unterstützt. Theoretisch könnten die Funktionen viel einfacher sein, wäre aber nicht mit MongoDB kompatibel. Diese komplexere Lösung kann jedoch verwendet werden, um die gesamten users
abzubilden Sammlung in einem einzigen Anruf, wenn Sie jemals müssen.