获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

Estructura tu base de datos

Esta guía cubre algunos de los conceptos clave en la arquitectura de datos y las mejores prácticas para estructurar los datos JSON en su Firebase Realtime Database.

Construir una base de datos correctamente estructurada requiere un poco de previsión. Lo que es más importante, debe planificar cómo se guardarán y recuperarán los datos para que el proceso sea lo más fácil posible.

Cómo se estructuran los datos: es un árbol JSON

Todos los datos de Firebase Realtime Database se almacenan como objetos JSON. Puede pensar en la base de datos como un árbol JSON alojado en la nube. A diferencia de una base de datos SQL, no hay tablas ni registros. Cuando agrega datos al árbol JSON, se convierte en un nodo en la estructura JSON existente con una clave asociada. Puede proporcionar sus propias claves, como ID de usuario o nombres semánticos, o se las pueden proporcionar mediante push() .

Si crea sus propias claves, deben estar codificadas en UTF-8, pueden tener un máximo de 768 bytes y no pueden contener archivos . , $ , # , [ , ] , / , o caracteres de control ASCII 0-31 o 127. Tampoco puede utilizar caracteres de control ASCII en los propios valores.

Por ejemplo, considere una aplicación de chat que permita a los usuarios almacenar un perfil básico y una lista de contactos. Un perfil de usuario típico se encuentra en una ruta, como /users/$uid . El usuario alovelace podría tener una entrada en la base de datos similar a esta:

{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      "contacts": { "ghopper": true },
    },
    "ghopper": { ... },
    "eclarke": { ... }
  }
}

Aunque la base de datos usa un árbol JSON, los datos almacenados en la base de datos se pueden representar como ciertos tipos nativos que corresponden a los tipos JSON disponibles para ayudarlo a escribir un código más fácil de mantener.

Las mejores prácticas para la estructura de datos

Evite el anidamiento de datos

Debido a que Firebase Realtime Database permite anidar datos hasta en 32 niveles de profundidad, es posible que sienta la tentación de pensar que esta debería ser la estructura predeterminada. Sin embargo, cuando obtiene datos en una ubicación de su base de datos, también recupera todos sus nodos secundarios. Además, cuando otorga a alguien acceso de lectura o escritura en un nodo de su base de datos, también le otorga acceso a todos los datos de ese nodo. Por lo tanto, en la práctica, es mejor mantener la estructura de datos lo más plana posible.

Para ver un ejemplo de por qué los datos anidados son malos, considere la siguiente estructura anidada múltiple:

{
  // This is a poorly nested data architecture, because iterating the children
  // of the "chats" node to get a list of conversation titles requires
  // potentially downloading hundreds of megabytes of messages
  "chats": {
    "one": {
      "title": "Historical Tech Pioneers",
      "messages": {
        "m1": { "sender": "ghopper", "message": "Relay malfunction found. Cause: moth." },
        "m2": { ... },
        // a very long list of messages
      }
    },
    "two": { ... }
  }
}

Con este diseño anidado, iterar a través de los datos se vuelve problemático. Por ejemplo, enumerar los títulos de las conversaciones de chat requiere que todo el árbol de chats , incluidos todos los miembros y mensajes, se descargue en el cliente.

Aplanar estructuras de datos

Si, en cambio, los datos se dividen en rutas separadas, lo que también se denomina desnormalización, se pueden descargar de manera eficiente en llamadas separadas, según sea necesario. Considere esta estructura aplanada:

{
  // Chats contains only meta info about each conversation
  // stored under the chats's unique ID
  "chats": {
    "one": {
      "title": "Historical Tech Pioneers",
      "lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
      "timestamp": 1459361875666
    },
    "two": { ... },
    "three": { ... }
  },

  // Conversation members are easily accessible
  // and stored by chat conversation ID
  "members": {
    // we'll talk about indices like this below
    "one": {
      "ghopper": true,
      "alovelace": true,
      "eclarke": true
    },
    "two": { ... },
    "three": { ... }
  },

  // Messages are separate from data we may want to iterate quickly
  // but still easily paginated and queried, and organized by chat
  // conversation ID
  "messages": {
    "one": {
      "m1": {
        "name": "eclarke",
        "message": "The relay seems to be malfunctioning.",
        "timestamp": 1459361875337
      },
      "m2": { ... },
      "m3": { ... }
    },
    "two": { ... },
    "three": { ... }
  }
}

Ahora es posible iterar a través de la lista de salas descargando solo unos pocos bytes por conversación, obteniendo metadatos rápidamente para enumerar o mostrar salas en una interfaz de usuario. Los mensajes se pueden recuperar por separado y mostrar a medida que llegan, lo que permite que la interfaz de usuario se mantenga receptiva y rápida.

Cree datos que escalan

Al crear aplicaciones, a menudo es mejor descargar un subconjunto de una lista. Esto es particularmente común si la lista contiene miles de registros. Cuando esta relación es estática y unidireccional, simplemente puede anidar los objetos secundarios debajo del principal.

A veces, esta relación es más dinámica, o puede ser necesario desnormalizar estos datos. Muchas veces puede desnormalizar los datos usando una consulta para recuperar un subconjunto de los datos, como se explica en Recuperar datos .

Pero incluso esto puede ser insuficiente. Considere, por ejemplo, una relación bidireccional entre usuarios y grupos. Los usuarios pueden pertenecer a un grupo y los grupos comprenden una lista de usuarios. Cuando llega el momento de decidir a qué grupos pertenece un usuario, las cosas se complican.

Lo que se necesita es una forma elegante de enumerar los grupos a los que pertenece un usuario y obtener solo datos para esos grupos. Un índice de grupos puede ayudar mucho aquí:

// An index to track Ada's memberships
{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      // Index Ada's groups in her profile
      "groups": {
         // the value here doesn't matter, just that the key exists
         "techpioneers": true,
         "womentechmakers": true
      }
    },
    ...
  },
  "groups": {
    "techpioneers": {
      "name": "Historical Tech Pioneers",
      "members": {
        "alovelace": true,
        "ghopper": true,
        "eclarke": true
      }
    },
    ...
  }
}

Puede notar que esto duplica algunos datos al almacenar la relación tanto en el registro de Ada como en el grupo. Ahora alovelace está indexado en un grupo y techpioneers aparece en el perfil de Ada. Entonces, para eliminar a Ada del grupo, debe actualizarse en dos lugares.

Esta es una redundancia necesaria para las relaciones bidireccionales. Le permite obtener de manera rápida y eficiente las membresías de Ada, incluso cuando la lista de usuarios o grupos asciende a millones o cuando las reglas de seguridad de Realtime Database impiden el acceso a algunos de los registros.

Este enfoque, que invierte los datos al enumerar los ID como claves y establecer el valor en verdadero, hace que verificar una clave sea tan simple como leer /users/$uid/groups/$group_id y verificar si es null . El índice es más rápido y mucho más eficiente que consultar o escanear los datos.

Próximos pasos

,

Esta guía cubre algunos de los conceptos clave en la arquitectura de datos y las mejores prácticas para estructurar los datos JSON en su Firebase Realtime Database.

Construir una base de datos correctamente estructurada requiere un poco de previsión. Lo que es más importante, debe planificar cómo se guardarán y recuperarán los datos para que el proceso sea lo más fácil posible.

Cómo se estructuran los datos: es un árbol JSON

Todos los datos de Firebase Realtime Database se almacenan como objetos JSON. Puede pensar en la base de datos como un árbol JSON alojado en la nube. A diferencia de una base de datos SQL, no hay tablas ni registros. Cuando agrega datos al árbol JSON, se convierte en un nodo en la estructura JSON existente con una clave asociada. Puede proporcionar sus propias claves, como ID de usuario o nombres semánticos, o se las pueden proporcionar mediante push() .

Si crea sus propias claves, deben estar codificadas en UTF-8, pueden tener un máximo de 768 bytes y no pueden contener archivos . , $ , # , [ , ] , / , o caracteres de control ASCII 0-31 o 127. Tampoco puede utilizar caracteres de control ASCII en los propios valores.

Por ejemplo, considere una aplicación de chat que permita a los usuarios almacenar un perfil básico y una lista de contactos. Un perfil de usuario típico se encuentra en una ruta, como /users/$uid . El usuario alovelace podría tener una entrada en la base de datos similar a esta:

{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      "contacts": { "ghopper": true },
    },
    "ghopper": { ... },
    "eclarke": { ... }
  }
}

Aunque la base de datos usa un árbol JSON, los datos almacenados en la base de datos se pueden representar como ciertos tipos nativos que corresponden a los tipos JSON disponibles para ayudarlo a escribir un código más fácil de mantener.

Las mejores prácticas para la estructura de datos

Evite el anidamiento de datos

Debido a que Firebase Realtime Database permite anidar datos hasta en 32 niveles de profundidad, es posible que sienta la tentación de pensar que esta debería ser la estructura predeterminada. Sin embargo, cuando obtiene datos en una ubicación de su base de datos, también recupera todos sus nodos secundarios. Además, cuando otorga a alguien acceso de lectura o escritura en un nodo de su base de datos, también le otorga acceso a todos los datos de ese nodo. Por lo tanto, en la práctica, es mejor mantener la estructura de datos lo más plana posible.

Para ver un ejemplo de por qué los datos anidados son malos, considere la siguiente estructura anidada múltiple:

{
  // This is a poorly nested data architecture, because iterating the children
  // of the "chats" node to get a list of conversation titles requires
  // potentially downloading hundreds of megabytes of messages
  "chats": {
    "one": {
      "title": "Historical Tech Pioneers",
      "messages": {
        "m1": { "sender": "ghopper", "message": "Relay malfunction found. Cause: moth." },
        "m2": { ... },
        // a very long list of messages
      }
    },
    "two": { ... }
  }
}

Con este diseño anidado, iterar a través de los datos se vuelve problemático. Por ejemplo, enumerar los títulos de las conversaciones de chat requiere que todo el árbol de chats , incluidos todos los miembros y mensajes, se descargue en el cliente.

Aplanar estructuras de datos

Si, en cambio, los datos se dividen en rutas separadas, lo que también se denomina desnormalización, se pueden descargar de manera eficiente en llamadas separadas, según sea necesario. Considere esta estructura aplanada:

{
  // Chats contains only meta info about each conversation
  // stored under the chats's unique ID
  "chats": {
    "one": {
      "title": "Historical Tech Pioneers",
      "lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
      "timestamp": 1459361875666
    },
    "two": { ... },
    "three": { ... }
  },

  // Conversation members are easily accessible
  // and stored by chat conversation ID
  "members": {
    // we'll talk about indices like this below
    "one": {
      "ghopper": true,
      "alovelace": true,
      "eclarke": true
    },
    "two": { ... },
    "three": { ... }
  },

  // Messages are separate from data we may want to iterate quickly
  // but still easily paginated and queried, and organized by chat
  // conversation ID
  "messages": {
    "one": {
      "m1": {
        "name": "eclarke",
        "message": "The relay seems to be malfunctioning.",
        "timestamp": 1459361875337
      },
      "m2": { ... },
      "m3": { ... }
    },
    "two": { ... },
    "three": { ... }
  }
}

Ahora es posible iterar a través de la lista de salas descargando solo unos pocos bytes por conversación, obteniendo metadatos rápidamente para enumerar o mostrar salas en una interfaz de usuario. Los mensajes se pueden recuperar por separado y mostrar a medida que llegan, lo que permite que la interfaz de usuario se mantenga receptiva y rápida.

Cree datos que escalan

Al crear aplicaciones, a menudo es mejor descargar un subconjunto de una lista. Esto es particularmente común si la lista contiene miles de registros. Cuando esta relación es estática y unidireccional, simplemente puede anidar los objetos secundarios debajo del principal.

A veces, esta relación es más dinámica, o puede ser necesario desnormalizar estos datos. Muchas veces puede desnormalizar los datos usando una consulta para recuperar un subconjunto de los datos, como se explica en Recuperar datos .

Pero incluso esto puede ser insuficiente. Considere, por ejemplo, una relación bidireccional entre usuarios y grupos. Los usuarios pueden pertenecer a un grupo y los grupos comprenden una lista de usuarios. Cuando llega el momento de decidir a qué grupos pertenece un usuario, las cosas se complican.

Lo que se necesita es una forma elegante de enumerar los grupos a los que pertenece un usuario y obtener solo datos para esos grupos. Un índice de grupos puede ayudar mucho aquí:

// An index to track Ada's memberships
{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      // Index Ada's groups in her profile
      "groups": {
         // the value here doesn't matter, just that the key exists
         "techpioneers": true,
         "womentechmakers": true
      }
    },
    ...
  },
  "groups": {
    "techpioneers": {
      "name": "Historical Tech Pioneers",
      "members": {
        "alovelace": true,
        "ghopper": true,
        "eclarke": true
      }
    },
    ...
  }
}

Puede notar que esto duplica algunos datos al almacenar la relación tanto en el registro de Ada como en el grupo. Ahora alovelace está indexado en un grupo y techpioneers aparece en el perfil de Ada. Entonces, para eliminar a Ada del grupo, debe actualizarse en dos lugares.

Esta es una redundancia necesaria para las relaciones bidireccionales. Le permite obtener de manera rápida y eficiente las membresías de Ada, incluso cuando la lista de usuarios o grupos asciende a millones o cuando las reglas de seguridad de Realtime Database impiden el acceso a algunos de los registros.

Este enfoque, que invierte los datos al enumerar los ID como claves y establecer el valor en verdadero, hace que verificar una clave sea tan simple como leer /users/$uid/groups/$group_id y verificar si es null . El índice es más rápido y mucho más eficiente que consultar o escanear los datos.

Próximos pasos