MongoDB - Database References
MongoDB - Modeling Data Relationships
Section titled “MongoDB - Modeling Data Relationships”Unlike traditional SQL databases, MongoDB’s flexible schema provides multiple ways to model relationships between data. The choice between embedding and referencing data is one of the most critical schema design decisions, impacting application performance, data integrity, and scalability.
Pattern 1: Embedding Documents (Denormalization)
Section titled “Pattern 1: Embedding Documents (Denormalization)”Embedding involves storing related data within a single parent document. This pattern is also known as denormalization.
Example: A Product with its Reviews
Section titled “Example: A Product with its Reviews”Consider a product that has a few reviews. Since the reviews are tightly coupled with the product, we can embed them as an array within the product document.
{ _id: ObjectId("..."), name: "Wireless Mouse", price: 25, // Reviews are embedded directly reviews: [ { username: "alice", rating: 5, comment: "Excellent product!" }, { username: "bob", rating: 4, comment: "Good value for the price." } ]}When to Use Embedding
Section titled “When to Use Embedding”- One-to-Few Relationships: Ideal when the number of related items is small and finite.
- Data Retrieval: When you need to retrieve the related data together with the main document, embedding provides the best performance as it requires a single database read.
- Atomicity: Updates to the document, including its embedded data, are atomic.
- Best For: Contact information for a user, items in a shopping cart, reviews for a product.
Pattern 2: Referencing Documents (Normalization)
Section titled “Pattern 2: Referencing Documents (Normalization)”Referencing, or normalization, involves storing related data in separate collections. You create a link between them by storing the _id of one document in another. This is the recommended approach for most ‘one-to-many’ relationships.
Example: Blog Posts and Authors
Section titled “Example: Blog Posts and Authors”An author can write many posts. Storing the author’s full details in every post would be redundant. Instead, we store the author’s _id in the post document.
// `authors` collection{ _id: ObjectId("author1"), name: "Chris", email: "chris@example.com"}
// `posts` collection{ _id: ObjectId("post123"), title: "Schema Design Best Practices", content: "...", // Manual reference to the author author_id: ObjectId("author1")}When to Use Referencing
Section titled “When to Use Referencing”- One-to-Many Relationships: When the ‘many’ side is large or unbounded (e.g., logs for an event, comments on a popular post).
- Avoiding Data Duplication: When the referenced data is large and updated frequently (e.g., user profiles).
- Many-to-Many Relationships: For example, a
studentscollection and acoursescollection, linked by a third collection that stores references to both. - Exceeding Document Size Limit: When embedding would cause documents to exceed the 16MB limit.
Resolving References: The Modern Approach with $lookup
Section titled “Resolving References: The Modern Approach with $lookup”The main drawback of referencing is that it requires a separate query to fetch the related data. The modern, efficient way to solve this is with the $lookup aggregation stage, which performs a server-side ‘join’.
// This aggregation pipeline finds a post and 'joins' its author informationdb.posts.aggregate([ { $match: { _id: ObjectId("post123") } // Find the specific post }, { $lookup: { from: "authors", // The collection to join with localField: "author_id", // The field from the input documents (posts) foreignField: "_id", // The field from the documents of the 'from' collection (authors) as: "authorDetails" // The new array field to add to the input documents } }])
// Result includes the post with author details embedded in an array:/*{ _id: ObjectId("post123"), title: "Schema Design Best Practices", author_id: ObjectId("author1"), authorDetails: [ { _id: ObjectId("author1"), name: "Chris", email: "chris@example.com" } ]}*/Legacy Pattern: DBRefs
Section titled “Legacy Pattern: DBRefs”DBRefs are a legacy, formal convention for referencing documents. A DBRef is a document with three fields: $ref (the collection name), $id (the _id of the referenced document), and an optional $db.
// Example of a document using a DBRef{ _id: ObjectId("user1"), name: "Tom Benzamin", // DBRef pointing to a document in the 'addresses' collection address: { "$ref": "addresses", "$id": ObjectId("addr1"), "$db": "w3" }}Why DBRefs are No Longer Recommended
Section titled “Why DBRefs are No Longer Recommended”While they exist, DBRefs are not the recommended way to model relationships. Modern best practices strongly favor manual references with the $lookup pipeline because:
- Lack of Automatic Resolution: Most MongoDB drivers do not automatically resolve DBRefs. You still need to write application-level code to perform a second query, just as you would with a manual reference.
- Added Complexity: They add unnecessary verbosity and complexity to your documents compared to simply storing an
ObjectId. $lookupis Superior: The$lookupaggregation stage is far more powerful and flexible. It works seamlessly with simple manual references and provides more control over the joined data.
Summary and Best Practice
Section titled “Summary and Best Practice”Start with embedding. If your data relationships are one-to-few and data is accessed together, embedding is faster. If embedding leads to data duplication, large documents, or you have one-to-many relationships, switch to manual references. Use the $lookup aggregation pipeline to efficiently retrieve related data when needed. Avoid DBRefs unless you have a specific, well-justified legacy reason or a niche use case requiring references to multiple collections from a single field.