Денормализация структур данных для доступа к частным данным в Firebase?

Я хочу создавать масштабируемые данные ( отслеживать личные данные пользователя).

Документация Firebase рекомендует вкладывать дочерние объекты в родительский следующим образом:

{
  "users": {
    "google:1234567890": {
      "displayName" : "Username A",
      "provider" : "google",
      "provider_id" : "1234567890",
      "todos": {
          "rec_id1": "Walk the dog",
          "rec_id2": "Buy milk",
          "rec_id3": "Win a gold medal in the Olympics",
          ...
      }
    }, ...
  }
}

(Где rec_id — уникальный ключ, сгенерированный Firebase.push().)

Но, как упоминалось в денормализация ваших данных — это нормально Я думаю, что было бы лучше структурировать это следующим образом:

{
  "users" : {
    "google:1234567890" : {
      "displayName" : "Username A",
      "provider" : "google",
      "provider_id" : "1234567890"
    }, ...
  },
  "todos" : {
    "google:1234567890" : {
      "rec_id1" : {
        "todo" : "Walk the dog"
      },
      "rec_id2" : {
        "todo" : "Buy milk"
      },
      "rec_id3" : {
        "todo" : "Win a gold medal in the Olympics"
      }, ...
    }, ...
  }
}

А затем, чтобы позволить пользователю только записывать/читать свои собственные данные, применяются следующие правила безопасности:

{
  "rules": {
    "users": {
      "$uid": {
        // grants write and read access to the owner of this user account whose uid must exactly match the key ($uid)
        ".write": "auth !== null && auth.uid === $uid",
        ".read": "auth !== null && auth.uid === $uid"
      }
    },
    "todos": {
      "$uid": {
        // grants write and read access to the owner of this user account whose uid must exactly match the key ($uid)
        ".write": "auth !== null && auth.uid === $uid",
        ".read": "auth !== null && auth.uid === $uid"
      }
    }
  }
}

Поскольку я новичок в базах данных такого типа, мне интересно, есть ли какие-либо недостатки в том, как я хотел бы их структурировать.

Не лучше ли разместить все задачи непосредственно под пользователем, как это было рекомендовано в первом примере?


person Steffen    schedule 13.03.2015    source источник


Ответы (1)


Во-первых, пара ресурсов, если вы еще не сталкивались с ними:

EDIT: вы, очевидно, столкнулись с этими ресурсами, поскольку они указаны в вашем вопросе, но я бы посоветовал еще несколько раз перечитать руководство по структурированию данных.


Это подводит нас к вашему сценарию...

Два способа настройки данных на самом деле выполняют почти одно и то же!

Вы заметите, однако, что первый пример на самом деле указан как анти-шаблон в руководство "Структурирование данных".

  • Выполнение этого вторым способом было бы полезно, если вы хотите загрузить данные пользователя, а затем задачи этого пользователя в другое время.
  • The way you have it set up is fine if you only have one user accessing each todo list.
    • For example, I do this in an app where I know that each user has one single location in a history list, and I only want to load the history of a user in certain scenarios.
    • /users/$userUid дает мне данные пользователя, а /history/$userUid дает мне историю пользователя.
    • Это позволяет легко сегментировать нагрузку.
  • Однако эта структура не дает никаких преимуществ, если списки дел используются разными пользователями и должны обновляться из нескольких источников.
  • Если вам нужен общий доступ, вы на правильном пути и просто должны использовать ключи в качестве ссылок.

Другой подход:

  • Вместо того, чтобы явно устанавливать объекты todo в /todos/$uid, вы можете отправить новый объект todo в /todos, чтобы он получил новый уникальный идентификатор (называемый ключом).
  • Затем вы добавляете этот ключ к правильному user дочернему объекту todos.
  • Это позволит вам сначала загрузить данные пользователя и получить только индексы (ключи) задач, к которым принадлежит пользователь, и
  • Затем вы можете независимо загрузить все те todos, к которым принадлежит пользователь.

Выполнение этого таким образом:

  • Не позволяйте пользовательским объектам становиться огромными
  • Разрешить нескольким пользователям обновлять один todo без необходимости обновлять его дочерние параметры в нескольких местах.
  • Получите масштабируемые данные, разделив их на отдельные пути.

Вот последний пример данных из "Создание данных, которые Весы" раздела руководства: (с некоторыми комментариями, которые я добавил)

  // An index to track Mary's memberships
  {
    "users": {
      "mchen": {
        "name": "Mary Chen",
        // index Mary's groups in her profile
        "groups": {
           // the value here doesn't matter, just that the key exists
           // these keys are used to figure out which groups should 
           // be loaded (at whatever appropriate time) for Mary,
           // without having to load all the group's data initially (just the keys). 
           "alpha": true,
           "charlie": true
        }
      },
      ...
    },
    // Here is /groups. In here, there would be a group with the key
    // 'alpha' and another one with the key 'charlie'. Once Mary's
    // data is loaded on the client, you would then proceed to load 
    // the groups from this list, since you know what keys to look for.
    "groups": { ... }
  }

Это обеспечивает более плоскую структуру.

Как говорится в документации,

да. Это необходимая избыточность для двусторонних отношений. Это позволяет нам быстро и эффективно получать сведения о членстве Мэри, даже когда список пользователей или групп исчисляется миллионами или когда правила безопасности и Firebase запрещают доступ к некоторым записям.


Итак, вопрос в том, как должны выглядеть ваши данные, чтобы пользователь мог иметь несколько списков дел, которыми можно было бы поделиться с другими пользователями?

Вот пример:

{
  "users" : {
    "google:1234567890" : {
      "displayName" : "Username A",
      "provider" : "google",
      "provider_id" : "1234567890",
      "todoLists" : {
        "todoList1": true,
        "todoList2": true
      }
    },
    "google:0987654321" : {
      "displayName" : "Username B",
      "provider" : "google",
      "provider_id" : "0987654321",
      "todoLists" : {
        "todoList2": true
      }
    }
  },
  "todoLists" : {
    "todoList1" : {
      // 'members' user for rules
      "members" : {
        "google:1234567890" : true
      },
      "records" : {
        "rec_id1" : {
          "todo" : "Walk the dog",
          "createdAt" : "1426240376047"
        },
        "rec_id2" : {
          "todo" : "Buy milk",
          "createdAt" : "1426240376301"
        },
        "rec_id3" : {
          "todo" : "Win a gold medal in the Olympics",
          "createdAt" : "1426240376301"
        }
      }
    },
    "todoList2" : {
      "members" : {
        "google:1234567890" : true,
        "google:0987654321" : true
      },
      "records" : {
        "rec_id4" : {
          "todo" : "Get present",
          "createdAt" : "1426240388047"
        },
        "rec_id5" : {
          "todo" : "Run a mile",
          "createdAt" : "1426240399301"
        },
        "rec_id6" : {
          "todo" : "Pet a cat",
          "createdAt" : "1426240400301"
        }
      }
    }
  }
}
  • В этом случае пользователь А загрузит оба списка, а пользователь Б загрузит только второй список. Если вы правильно настроите свои правила, все будет хорошо.
  • При работе вы сначала загрузите данные пользователя, а затем загрузите каждый список задач в пользовательские списки задач из /todoList.

  • Но на самом деле все это совершенно не нужно, если вы создаете приложение, в котором у одного пользователя есть один список задач с элементами задач, которые имеют только один контент.
  • Side note, these "ids" should probably be a unique key, which can be accomplished using Firebase.push().

В заключение все сводится к тому, как и когда вашему приложению нужны ваши данные, как часто данные обновляются и кем, а также к тому, чтобы свести к минимуму количество ненужных операций чтения и наблюдателей. Пространство обычно дешевое, операции (и наблюдатели) обычно нет.

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

«Таким образом, индекс быстрее и намного эффективнее. Позже, когда мы будем говорить о защите данных, эта структура также будет очень важна. Поскольку правила безопасности и Firebase не могут делать какие-либо «содержит» в списке дочерних узлов , мы будем широко использовать такие ключи».


Это рано, и я надеюсь, что не болтаю, но я столкнулся с теми же вопросами, когда впервые перешел от знания MySql к использованию неструктурированного, поэтому я надеюсь, что это поможет!

person sbolel    schedule 13.03.2015
comment
Спасибо за полный ответ! Поскольку мне не нужны отношения «многие ко многим», я думаю, что я выберу свое решение /users/$userUid и /todos/$userUid. Но я все еще думаю о том, могу ли я сожалеть об этом компромиссе в будущем. Интересно, можно ли разрешить пользователю делиться своими данными с другими пользователями позже. Возможно, много-много членства в группе пользователей, как указано в stackoverflow.com/questions/29021067/ сделает? - person Steffen; 15.03.2015
comment
(Для моего решения я предполагаю, что если бы я установил свои правила на .read": "auth !== null && auth.uid === $uid", это предотвратило бы ненужное чтение объектов, для которых у других пользователей нет разрешений на доступ к данным, отличным от их собственных, в /users/ и /todos/. Точно так же, как индекс сведет к минимуму ненужные чтения и просмотры.) - person Steffen; 15.03.2015