MongoDB, jako baza danych NoSQL, przechowuje dane w formie dokumentów, które mogą być zagnieżdżone lub połączone poprzez referencje. Mongoose, jako popularna biblioteka do obsługi MongoDB w Node.js, oferuje dwie główne metody tworzenia relacji między dokumentami: embeddowanie (czyli zagnieżdżanie dokumentów) oraz referencje (czyli odwołania do innych dokumentów). W tym artykule omówimy oba podejścia, pokażemy przykłady ich implementacji oraz wskażemy, kiedy warto stosować każde z nich.
Embeddowanie dokumentów: Schematy zagnieżdżone
Embeddowanie polega na zagnieżdżaniu jednego dokumentu wewnątrz innego dokumentu. Jest to metoda wygodna i wydajna, ponieważ przechowuje dane razem, co pozwala na szybkie odczytywanie informacji bez dodatkowych operacji łączenia.
Przykład embeddowania dokumentów
Załóżmy, że tworzymy aplikację, w której mamy kolekcję User, a każdy użytkownik ma kolekcję Address przechowującą jego adresy. Możemy zagnieździć dokument Address bezpośrednio w dokumencie User.
const mongoose = require('mongoose');
// Schemat zagnieżdżony dla adresu
const addressSchema = new mongoose.Schema({
street: String,
city: String,
zipCode: String
});
// Schemat użytkownika, który zawiera adres jako zagnieżdżony dokument
const userSchema = new mongoose.Schema({
name: String,
email: String,
addresses: [addressSchema] // Pole addresses jest tablicą zagnieżdżonych dokumentów Address
});
const User = mongoose.model('User', userSchema);
Dzięki powyższej strukturze możemy przechowywać wiele adresów użytkownika bez potrzeby tworzenia osobnej kolekcji Address.
Przykład dodania użytkownika z zagnieżdżonym adresem
const newUser = new User({
name: 'Jan Kowalski',
email: 'jan@example.com',
addresses: [
{ street: 'Kwiatowa 5', city: 'Warszawa', zipCode: '00-001' },
{ street: 'Ogrodowa 12', city: 'Kraków', zipCode: '30-001' }
]
});
newUser.save()
.then(() => console.log('Użytkownik zapisany z zagnieżdżonymi adresami'))
.catch(error => console.log('Błąd zapisu:', error));
Powyższy kod tworzy użytkownika z dwoma adresami zapisanymi w jednym dokumencie User.
Kiedy używać embeddowania?
Embeddowanie dokumentów jest korzystne, gdy:
- Dane są ze sobą mocno powiązane i są zwykle pobierane razem (np. adresy użytkownika),
- Dokument zagnieżdżony jest mały i nie zmienia się niezależnie od głównego dokumentu.
Referencje: Relacje typu „populate”
Referencje polegają na przechowywaniu identyfikatora jednego dokumentu w innym dokumencie, zamiast pełnych danych. Dzięki temu możemy połączyć dokumenty między kolekcjami, a Mongoose oferuje funkcję populate(), która pozwala na automatyczne ładowanie danych z referencji.
Przykład tworzenia referencji
Załóżmy, że mamy kolekcje User i Post, w której przechowujemy posty użytkowników. W takim przypadku lepiej jest przechowywać referencje do użytkownika w dokumencie Post niż zagnieżdżać je bezpośrednio, co umożliwi nam pobranie wszystkich postów użytkownika w przyszłości.
// Schemat użytkownika
const userSchema = new mongoose.Schema({
name: String,
email: String
});
const User = mongoose.model('User', userSchema);
// Schemat posta z referencją do użytkownika
const postSchema = new mongoose.Schema({
title: String,
content: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } // Referencja do użytkownika
});
const Post = mongoose.model('Post', postSchema);
Przykład dodania posta z referencją do użytkownika
Najpierw utworzymy użytkownika, a następnie dodamy post z referencją do tego użytkownika:
const newUser = new User({
name: 'Anna Nowak',
email: 'anna@example.com'
});
newUser.save()
.then(user => {
const newPost = new Post({
title: 'Mój pierwszy post',
content: 'To jest zawartość posta',
author: user._id // Referencja do użytkownika
});
return newPost.save();
})
.then(() => console.log('Post zapisany z referencją do użytkownika'))
.catch(error => console.log('Błąd zapisu:', error));
Pobieranie danych za pomocą populate()
Aby pobrać dane posta wraz z informacją o autorze, używamy metody .populate():
Post.find()
.populate('author') // Ładuje dane autora do pola author w dokumencie Post
.then(posts => console.log('Posty z załadowanymi autorami:', posts))
.catch(error => console.log('Błąd pobierania:', error));
Dzięki populate() dane autora są automatycznie dołączane do wyników, co pozwala na wygodne odczytanie pełnych informacji o poście i jego autorze.
Kiedy używać referencji?
Referencje są szczególnie przydatne, gdy:
- Dane są niezależne i mogą być pobierane osobno (np. użytkownik i jego posty),
- Chcemy przechowywać duże zbiory danych powiązanych z danym dokumentem (np. posty użytkownika),
- Dane często się zmieniają i wymagają niezależnych aktualizacji.
Różnice między embeddowaniem a referencjami i kiedy ich używać
| Embeddowanie | Referencje |
|---|---|
| Dane przechowywane są bezpośrednio wewnątrz dokumentu | Przechowywany jest identyfikator dokumentu innej kolekcji |
| Pobieranie danych jest szybsze (bez dodatkowych zapytań) | Pobieranie wymaga dodatkowego zapytania (populate()) |
| Nadaje się do danych, które są często pobierane razem (np. adresy użytkownika) | Nadaje się do danych, które są często pobierane osobno (np. posty użytkownika) |
| Idealne dla małych, niezależnych zestawów danych | Idealne dla dużych, powiązanych zestawów danych |
| Aktualizacja dokumentów zagnieżdżonych wymaga aktualizacji całego dokumentu | Każdy dokument może być aktualizowany niezależnie |
Podsumowując:
- Embeddowanie jest świetne do przechowywania małych, nierozłącznych danych, które są często używane razem z głównym dokumentem.
- Referencje sprawdzają się, gdy mamy do czynienia z dużymi zestawami danych, które są często pobierane osobno i wymagają niezależnych aktualizacji.
Podsumowanie
Zarówno embeddowanie, jak i referencje są przydatne, w zależności od wymagań aplikacji i typu danych:
- Embeddowanie jest szybkie i pozwala na przechowywanie małych, powiązanych danych.
- Referencje umożliwiają tworzenie bardziej elastycznych struktur danych, które można pobierać niezależnie i skalować.
Wybierając między tymi metodami, należy kierować się rozmiarem danych, ich powiązaniami i częstotliwością dostępu. Dzięki temu nasze aplikacje będą wydajne, elastyczne i łatwe w zarządzaniu.