DEV Community

Nicolás Castro Garcia
Nicolás Castro Garcia

Posted on

MongoDB: Embedding vs Referencing

Cuando modelamos datos en MongoDB, una de las primeras decisiones es elegir entre Embedding o Referencing.

Ninguna opción es mejor. Depende principalmente de cómo vamos a leer y modificar nuestros datos.

1. Embedding

Con embedding, guardamos los datos directamente dentro del documento.

Por ejemplo:

Collection:Users

{
  _id: 1,
  name: "Nicolas",
  address: {
    city: "Buenos Aires",
    country: "Argentina"
  }
}
Enter fullscreen mode Exit fullscreen mode

El campo address forma parte del documento User, por lo que NO necesitamos almacenarla en otra collection.

Conviene usar embedding cuando:

  • Los datos son pequeños
  • Normalmente los necesitamos juntos
  • Queremos obtener todo con una sola consulta
  • Los datos no van a ser utilizados por múltiples documentos

2. Referencing

Con referencing, guardamos el ID de otro documento.

Collection:Employees

{
  _id: 1,
  name: "Nicolas",
  companyId: ObjectId("1")
}
Enter fullscreen mode Exit fullscreen mode

Y la empresa vive en otra collection:

Collection:Companies

{
  _id: ObjectId("1"),
  name: "Amplify"
}
Enter fullscreen mode Exit fullscreen mode

La relación sería:

diagrama relacional

Conviene usar referencing cuando:

  • La entidad relacionada tiene su propio ciclo de vida
  • Muchos documentos utilizan la misma información
  • Requiere de una actualización independiente

También hay que tener en cuenta cómo cambian los datos.

Si muchos documentos comparten la misma información y queremos mantener una única fuente de verdad, referencing suele tener más sentido


3. Desnormalización

Usar referencing no significa que no podamos duplicar información

Por ejemplo:

Imaginemos que tenemos dos collections: una de empleados (Employees) y otra de Empresas (Companies)

Collection:Employees

{
  _id: 1,
  name: "Nicolas",
  companyId: ObjectId("..."), // id de referencia
  companyName: "Amplify"
}
Enter fullscreen mode Exit fullscreen mode

Seguimos teniendo la referencia a Company, pero también guardamos el nombre de la empresa en el documento.

Esto se conoce como desnormalización

¿Por qué hacerlo?

Porque ahora podemos mostrar la información del empleado sin tener que hacer otra consulta para obtener el nombre de la empresa:

db.employees.findOne({ _id: 1 })
Enter fullscreen mode Exit fullscreen mode

Ahora bien, el problema es que companyName está duplicado.

Si la empresa cambia su nombre, tenemos que actualizar también los documentos que lo tienen almacenado.

En general conviene utilizar desnormalización cuando los datos duplicados son datos que no van a ser actualizados nunca, o casi nunca.

En nuestro ejemplo el nombre de una empresa no es algo que suele cambiar todo el tiempo. Esto nos permite hacer lecturas más simples, ahorrándonos consultas a la base de datos, con la contra de tener que realizar una actualización posiblemente grande en el caso de que la empresa cambie su nombre.

La idea no es evitar la duplicación a toda costa, sino duplicar información de manera intencional cuando beneficia nuestros patrones de lectura.


4. findOne() vs $lookup

Imaginemos que NO usamos desnormalización y volvamos al ejemplo del empleado:

Collection:Employees

{
  name: "Nicolas",
  companyId: ObjectId("...")
}
Enter fullscreen mode Exit fullscreen mode

Podemos obtener la empresa haciendo una segunda consulta:

const employee = db.employees.findOne({ name: "Nicolas" })

const company = db.companies.findOne({
  _id: employee.companyId
})
Enter fullscreen mode Exit fullscreen mode

Tenemos DOS operaciones contra MongoDB.

Otra opción es utilizar $lookup:

db.employees.aggregate([
  {
    $match: { name: "Nicolas" }
  },
  {
    $lookup: {
      from: "companies",
      localField: "companyId",
      foreignField: "_id",
      as: "company"
    }
  }
])
Enter fullscreen mode Exit fullscreen mode

$lookup nos permite relacionar documentos de distintas collections dentro de una aggregation.

Para obtener un solo empleado, hacer dos findOne() puede ser perfectamente razonable

Pero si estamos obteniendo muchos empleados y necesitamos también sus empresas, hacer una consulta adicional por cada empleado puede generar el problema conocido como consultas N+1. En ese caso, $lookup puede ser una mejor alternativa

Y algo importante: guardar un ID como referencia no crea una clave foránea. MongoDB no sigue automáticamente esa referencia. Somos nosotros quienes decidimos cómo obtener el documento relacionado. Ya sea con otra consulta, con $lookup, o incluso evitando la consulta mediante desnormalización.


TLDR

Pensá primero en cómo se van a leer y modificar los datos.

Embedding conviene utilizarse cuando los datos pertenecen sólo al documento y normalmente se utilizan juntos.

Referencing cuando muchos documentos van a utilizar la misma información, y esa información va a ser actualizada con frecuencia.

Desnormalización cuando queremos contar con una referencia a un documento de otra collection, pero para ahorrarnos consultas extras duplicamos información que no va a ser actualizada con frecuencia

Y para obtener los datos referenciados podemos usar consultas separadas con findOne() o $lookup cuando necesitamos combinar muchos documentos.

MongoDB no nos obliga a elegir una única estrategia. El objetivo es elegir el modelo que mejor se adapte a los patrones de acceso de nuestra aplicación.


Referencias

Top comments (0)