Masz dwa możliwe sposoby, dzięki którym użytkownik może śledzić innego użytkownika; bezpośrednio lub pośrednio przez grupę, w którym to przypadku użytkownik bezpośrednio podąża za grupą. Zacznijmy od przechowywania tych bezpośrednich relacje między użytkownikami i grupami:
{
_id: "userA",
followingUsers: [ "userB", "userC" ],
followingGroups: [ "groupX", "groupY" ]
}
Teraz będziesz chciał móc szybko dowiedz się, których użytkowników śledzi użytkownik A, bezpośrednio lub pośrednio. Aby to osiągnąć, możesz zdenormalizować grupy, które obserwuje użytkownik A. Załóżmy, że grupy X i Y są zdefiniowane w następujący sposób:
{
_id: "groupX",
members: [ "userC", "userD" ]
},
{
_id: "groupY",
members: [ "userD", "userE" ]
}
Na podstawie tych grup i bezpośrednich relacji użytkownika A możesz wygenerować subskrypcje między użytkownikami. Pochodzenie subskrypcji są przechowywane z każdą subskrypcją. Dla przykładowych danych subskrypcje wyglądałyby tak:
// 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" ] }
Możesz łatwo wygenerować te subskrypcje, korzystając z wywołania mapy-redukuj-finalizuj dla pojedynczego użytkownika. Jeśli grupa zostanie zaktualizowana, wystarczy ponownie uruchomić redukcję mapy dla wszystkich użytkowników, którzy śledzą grupę, a subskrypcje zostaną ponownie zaktualizowane.
Redukcja mapy
Następujące funkcje redukcji mapy wygenerują subskrypcje dla jednego użytkownika.
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);
}
Następnie możesz uruchomić map-reduce dla pojedynczego użytkownika, określając zapytanie, w tym przypadku dla userA
.
db.users.mapReduce(map, reduce, { finalize: finalize, query: { _id: "userA" }})
Kilka uwag:
- Powinieneś usunąć poprzednie subskrypcje użytkownika, przed uruchomieniem map-reduce dla tego użytkownika.
- Jeśli aktualizujesz grupę, powinieneś uruchomić map-reduce dla wszystkich użytkowników, którzy obserwują grupę.
Powinienem zauważyć, że te funkcje redukcji mapy okazały się bardziej złożone niż to, o czym myślałem , ponieważ MongoDB nie obsługuje tablic jako wartości zwracanych funkcji zmniejszania. Teoretycznie funkcje mogłyby byłby znacznie prostszy, ale nie byłby kompatybilny z MongoDB. Jednak to bardziej złożone rozwiązanie może być użyte do mapowania-redukcji całych users
odbiór w jednym połączeniu, jeśli kiedykolwiek będziesz musiał.