Подтвердить что ты не робот

Twitter-приложение с использованием MongoDB

Я создаю приложение, которое использует классический механизм "follow" (тот, который используется Twitter и множество других приложений в Интернете). Я использую MongoDB. Однако у моей системы есть разница: пользователь может следить за группами пользователей. Это означает, что если вы будете следовать за группой, вы автоматически будете следовать за всеми пользователями, входящими в эту группу. Конечно, пользователи могут принадлежать более чем одной группе.

Вот что я придумал:

  • когда пользователь A следует пользователю B, идентификатор пользователя B добавляется во встроенный массив (называемый following) в документе пользователя A
  • для unfollowing, я удаляю идентификатор следующего пользователя из массива following Группы
  • работают одинаково: когда пользователь A следует группе X, идентификатор группы X добавляется в массив following. (Я фактически добавляю DBRef, поэтому я знаю, связано ли соединение с пользователем или группой.)

  • когда мне нужно проверить, следует ли пользователь A группа X, я просто ищу идентификатор группы пользователя A массив.

  • когда мне нужно проверить, соответствует ли пользователь A пользователю B, все становится немного сложнее. Каждый пользовательский документ имеет встроенный массив, в котором перечислены все группы, к которым принадлежит пользователь. Поэтому я использую условие $or для проверки того, является ли пользователь A либо непосредственно пользователем пользователя B, либо через группу. Как это:

    db.users.find({'$or':{'following.ref.$id':$user_id,'following.ref.$ref','users'},{'following.ref.$id':{'$in':$group_ids},'following.ref.$ref':'groups'}}})

Это отлично работает, но я думаю, что у меня есть несколько вопросов. Например, как показать список подписчиков для конкретного пользователя, включая разбиение на страницы? Я не могу использовать skip() и limit() во встроенном документе.

Я мог бы изменить дизайн и использовать коллекцию userfollow, которая выполнила бы ту же работу встроенного документа following. Проблема с этим подходом, который я пробовал, заключается в том, что с условием $or, которое я использовал ранее, пользователи, следующие за двумя группами, содержащими один и тот же пользователь, будут перечислены дважды. Чтобы этого избежать, я мог бы использовать группу или MapReduce, которые я действительно делал, и это работает, но я бы хотел избежать этого, чтобы упростить ситуацию. Может быть, мне просто нужно думать из коробки. Или, возможно, я предпринял неправильный подход с обеих попыток. Кто-то уже должен был сделать подобную вещь и придумал лучшее решение?

(На самом деле это следующий вопрос этого старого вопроса. Я решил опубликовать новый вопрос, чтобы лучше объяснить мою новую ситуацию, надеюсь, что это не проблема.)

4b9b3361

Ответ 1

У вас есть два возможных способа, которыми пользователь может следовать за другим пользователем; либо прямо, либо косвенно через группу, и в этом случае пользователь непосредственно следует за группой. Начнем с сохранения этих прямых отношений между пользователями и группами:

{
  _id: "userA",
  followingUsers: [ "userB", "userC" ],
  followingGroups: [ "groupX", "groupY" ]
}

Теперь вам нужно будет быстро узнать, какие пользователи пользователя A следуют прямо или косвенно. Для этого вы можете денормализовать группы, которые следуют пользователю A. Пусть говорят, что группы X и Y определяются следующим образом:

{
  _id: "groupX",
  members: [ "userC", "userD" ]
},
{
  _id: "groupY",
  members: [ "userD", "userE" ]
}

Основываясь на этих группах и пользовательских отношений прямых отношений A, вы можете создавать подписки между пользователями. Происхождение подписки сохраняются с каждой подпиской. Для данных примера подписки будут выглядеть так:

// 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" ] }

Вы можете с легкостью сгенерировать эти подписки, используя вызов map-reduce-finalize для отдельного пользователя. Если группа обновлена, вам нужно только повторно запустить снимок карты для всех пользователей, следующих за группой, и подписки будут обновлены снова.

Карта-свертка

Следующие функции уменьшения числа будут генерировать подписки для одного пользователя.

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);
}

Затем вы можете запустить сокращение карты для одного пользователя, указав запрос, в этом случае для userA.

db.users.mapReduce(map, reduce, { finalize: finalize, query: { _id: "userA" }})

Несколько примечаний:

  • Вы должны удалить предыдущие подписки пользователя, прежде чем запускать снимок карты для этого пользователя.
  • Если вы обновляете группу, вы должны запустить снимок карты для всех пользователей, следующих за группой.

Следует отметить, что эти функции уменьшения отображения оказались более сложными, чем то, что я имел в виду, поскольку MongoDB не поддерживает массивы как возвращаемые значения функций сокращения. Теоретически функции могут быть намного проще, но не совместимы с MongoDB. Однако это более сложное решение может быть использовано для преобразования всей коллекции users в один вызов, если вам когда-либо понадобится.