Mongo DB Schema Design patterns

Inheritance pattern

When to use

How to apply

Use the aggregation framework to apply the inheritance pattern

Example

Book store app, where books can be audio book, printed book, and ebook.

{
    ...
    "title": "", // common
    "author": "", // common
    "authorId": "", // common
    "rating": 4, 
    "genres": [""], // common
    "pages": 0,
    "price": 0, // common
    "publisher": "", // common
    ...
    "product_type":"ebook", // Defines the type of document
    "download_url": "",
}

Audiobook where there are audiobook specific fields.

{
    ...
    "download_url":""
    ...
    "product_type": "audiobook",
    "narrators": [""],
    "duration": "",
    "time_by_chapter": [{"chapter": "","end":""}]
}
{
    ...
    "product_type": "printedbook",
    // no requirement of download_url so remove it.
}

Consider if there is no product type is not present in the books collection but all three types of books are present in the collection then aggregation pipeline can be applied to add product type field.

var apply_inheritance_pattern_to_books_pipeline = [
    $project: {
        _id: "$_id",
        product_id:: "$product_id",
        product_type: {
            $ifNull: ["$product_type", "unspecified"],
        }, // just adding product type field with unspecified value.
        description: {
            $ifNull: [
                "$desc",
                "$description",
                "$details",
                "Unspecified",
            ]
        },
        authors: {
            $ifNull: [
                "$authors",
                ["$authors"],
                "Unspecified",
            ],
        },
        publisher: "$publisher",
        ...
    },
    {
        $merge: {
            into: "books",
            on: "_id",
            whenMatched: "replace",
            whenNotMatched: "discard",
        },
    },
]

db.books.aggregate(apply_inheritance_pattern_to_books_pipeline)
var cleanup_audiobook_entries_in_book_pipeline = [
    {
        $match: {
            $and: [{product_type: "Unspecified"},{length_minutes: {$gte: 0}}],
        },
    },
    {
        $set: {product_type: "audiobook"},
    },
    {
        $merge: {
            into: "books",
            on: "_id",
            whenMatched: "replace",
            whenNotMatched: "discard",
        },
    },
];

Computed Pattern

Common computations:

Mathematical:

For example rating

NewAverage = (avgRating*reviewCount + newRating)/(reviewCount+1) 

Roll-up:

Merging data together. Allows view data as a whole.

use bookstore;

var rollup_product_type_and_number_of_authors_pipeline = [
    {
        $group: {
            _id: "$product_type",
            count: {
                $sum: 1,
            },
            averageNumberOfAuthors: {
                $avg: {
                    $size: "$authors",
                },
            },
        },
    },
]

db.books.aggregate(rollup_product_type_and_number_of_authors_pipeline )

Extended Reference pattern

Embed data from other documents into main document to reduce lookup (equivalent of joins in SQL) operation on each query.

Approximation Pattern

This pattern generates statistically valid number that is not exact

When to use

Anti-patterns