Las relaciones uno a uno en MongoDB pueden realizarse de varias maneras. Algunas más óptimas y escalables a largo plazo que otras.
Relaciones uno a uno embebidas vs. referenciadas
Ocurre con las relaciones uno a uno embebidas que estamos repitiendo N cantidad de veces el mismo objeto en múltiples documentos. Esto, además de ocupar espacio innecesario en la base de datos, puede ocasionar problemas si tenemos la necesidad de actualizar todos esos documentos repetidos por un cambio de nombre de categoría o similar.
Para resolver este problema, las relaciones uno a uno pueden realizarse de forma referencial. O sea, guarda el ID de un documento, dentro de otro. Muy similar a lo que solemos realizar en SQL guardando llaves foráneas en registros de otras tablas.
Relaciones referenciadas
Prepara tu aplicación para resolver este tipo de relación de la siguiente manera.
Paso 1: adaptación de los esquemas
Crea un esquema que hará posteriormente la colección de documentos que estarán referenciados en otros documentos.
Prepara tu esquema principal. El documento que contendrá la relación referenciada a través de Brand | Types.ObjectId para indicarle a Mongoose qué tiene que esperar en esa propiedad.
Paso 2: preparación de los DTO
Prepara el DTO de creación de tu esquema principal, incorporando la propiedad que contendrá la referencia al otro documento.
Has guardado un MongoID dentro de un documento. Es momento de realizar un "JOIN" para traer la información del mismo.
En MongoDB, los Join son denominados "Populates", lo que hará Mongo aquí es ir a buscar el objeto a la colección a la cual pertenece.
De esta forma, el GET devolverá el objeto principal, además del objeto referenciado dentro.
Las relaciones referenciadas son más profesionales y escalables. Solucionan muchos problemas típicos de bases de datos como la redundancia de los mismos y facilita las consultas.
Contribución creada por: Kevin Fiorentino (Platzi Contributor).
Código de ejemplo para relaciones uno a uno referenciadas
🚨 Es importante que tengas ya esquemas, controladores, servicios y dtos de las entidades:
Brand
Customer
Order
User
Deberías estar avanzado en ellos y ahora los vamos a usar para empezar a hacer relaciones, pero no te preocupes puedes encontrar estos afectados en la rama 13 y compararlo con lo que tú llevas.
// src/products/entities/brand.entity.tsimport{Prop,Schema,SchemaFactory}from'@nestjs/mongoose';import{Document}from'mongoose';@Schema()exportclassBrandextendsDocument{@Prop({ required:true, unique:true})// 👈 is unique name:string;@Prop() image:string;}exportconstBrandSchema=SchemaFactory.createForClass(Brand);
// src/products/entities/product.entity.tsimport{Document,Types}from'mongoose';import{Brand}from'./brand.entity';exportclassProductextendsDocument{@Prop({ type:Types.ObjectId, ref:Brand.name})// 👈 relation brand:Brand|Types.ObjectId;// 👈 new field}
// src/products/dtos/products.dtos.tsimport{...,IsMongoId,// 👈 new decorator}from'class-validator';exportclassCreateProductDto{...@IsNotEmpty()@IsMongoId()readonly brand:string;// 👈 new field}
Las relaciones uno a uno en MongoDB pueden realizarse de varias maneras. Algunas más óptimas y escalables a largo plazo que otras.
Relaciones uno a uno embebidas vs. referenciadas
Ocurre con las relaciones uno a uno embebidas que estamos repitiendo N cantidad de veces el mismo objeto en múltiples documentos. Esto, además de ocupar espacio innecesario en la base de datos, puede ocasionar problemas si tenemos la necesidad de actualizar todos esos documentos repetidos por un cambio de nombre de categoría o similar.
Para resolver este problema, las relaciones uno a uno pueden realizarse de forma referencial. O sea, guarda el ID de un documento, dentro de otro. Muy similar a lo que solemos realizar en SQL guardando llaves foráneas en registros de otras tablas.
Relaciones referenciadas
Prepara tu aplicación para resolver este tipo de relación de la siguiente manera.
Paso 1: adaptación de los esquemas
Crea un esquema que hará posteriormente la colección de documentos que estarán referenciados en otros documentos.
Prepara tu esquema principal. El documento que contendrá la relación referenciada a través de Brand | Types.ObjectId para indicarle a Mongoose qué tiene que esperar en esa propiedad.
Paso 2: preparación de los DTO
Prepara el DTO de creación de tu esquema principal, incorporando la propiedad que contendrá la referencia al otro documento.
// products/products.dtos.ts
import { IsMongoId } from 'class-validator';
export class CreateProductDto {
@IsNotEmpty()
@IsMongoId()
readonly brand: string;
}
Paso 3: GET del documento referenciado
Has guardado un MongoID dentro de un documento. Es momento de realizar un "JOIN" para traer la información del mismo.
En MongoDB, los Join son denominados "Populates", lo que hará Mongo aquí es ir a buscar el objeto a la colección a la cual pertenece.
De esta forma, el GET devolverá el objeto principal, además del objeto referenciado dentro.
Las relaciones referenciadas son más profesionales y escalables. Solucionan muchos problemas típicos de bases de datos como la redundancia de los mismos y facilita las consultas.
Contribución creada por: Kevin Fiorentino (Platzi Contributor).
Código de ejemplo para relaciones uno a uno referenciadas
🚨 Es importante que tengas ya esquemas, controladores, servicios y dtos de las entidades:
Brand
Customer
Order
User
Deberías estar avanzado en ellos y ahora los vamos a usar para empezar a hacer relaciones, pero no te preocupes puedes encontrar estos afectados en la rama 13 y compararlo con lo que tú llevas.
🚨 Es importante que esta clase tengas ya esquemas, controladores, servicios y dtos de las entidades:
Brand
Customer
Order
User
Que deberías estar avanzado en ellos y ahora los vamos a usar para empezar a hacer relaciones, pero no te preocupes puedes encontrar estos afectados en la rama 13 y compararlo con lo que tú llevas.
Para el findOne en lugar de usar el método findById() usaremos el método findOne() que es el que admite el populate.
A este método le tendremos que especificar por que campo queremos buscar.
Ejemplo:
async findOne(id: string) {
const product = await this.productModel
.findOne({ _id: id })
.populate('brand')
.exec();
if (!product) {
throw new NotFoundException(`Product #${id} not found`);
}
return product;
}
Me parece mucho mejor este tipo de referencia a pesar de trabajar en una BD no relacional, al final se optimiza espacio en memoria, y como lo dice nicolás, es mucho mejor para las actualizaciones en este caso de una categoría.
Está almacenando los valores de las referencias como tipo string, no como tipo object, algo muy importante al momento de hacer consultas más avanzadas con aggregations
Yo lo solucioné usando mejor la clase Schema:
Lo diferente es que las sentencias de filtros y paginados se van encadenando según sea necesario, y en todo el método solo hay una sentencia que retorne. Espero les sea de utilidad.