Project: Product Catalog (Attribute Pattern)
Introduction
E-commerce catalogs mix shared fields (name, SKU, price) with category-specific attributes (phone storage vs book ISBN). MongoDB’s flexible schema fits the Attribute pattern—one products collection with varying shape. This project models SKUs, queries with $elemMatch, and aggregates inventory.
Prerequisites
Project Goals
| Feature | Example |
|---|---|
| Shared fields | name, category, price, sku |
| Dynamic attributes | { key: "storage", value: "256GB" } |
| Variants array | color, size, stock per variant |
| Inventory stats | total stock by category |
Document Shape
Phone:
Book:
Alternative: top-level dynamic keys with attributes.storage—pick one convention per team and document it.
Indexes
db.products.createIndex({ sku: 1 }, { unique: true })
db.products.createIndex({ category: 1, price: 1 })
db.products.createIndex({ "attributes.key": 1, "attributes.value": 1 })
db.products.createIndex({ "variants.sku": 1 })Query: Find by Attribute
db.products.find({
category: "electronics",
attributes: {
$elemMatch: { key: "storage", value: "256GB" }
}
})Find variant with low stock:
db.products.find({
variants: {
$elemMatch: { stock: { $lt: 10 } }
}
})Aggregation: Stock by Category
PyMongo: Update Variant Stock
from pymongo import ReturnDocument
def decrement_variant_stock(products, product_sku: str, variant_sku: str, qty: int = 1):
return products.find_one_and_update(
{"sku": product_sku, "variants.sku": variant_sku, "variants.stock": {"$gte": qty}},
{"$inc": {"variants.$.stock": -qty}},
return_document=ReturnDocument.AFTER,
)Positional $ updates the matched array element—atomic per document.
Warning
Multi-SKU orders spanning documents need transactions or idempotent order records—not blind multi-updates without checks.
Schema Validation (Optional)
Post-Project Checklist
- Consistent
attributesarray shape -
$elemMatchqueries tested - Aggregation inventory report runs
- Unique
skuat product and variant level documented
FAQ
Attribute pattern vs many collections?
One collection simplifies cross-category search; separate collections per category help when schemas diverge completely.
Relational alternative?
Normalized products, attributes, variants tables in MySQL—more JOINs, stricter schema.
Search facets?
Atlas Search or text indexes on name; facet counts via $facet — Advanced Aggregation.
Price history?
Separate price_history collection with productId reference.
Cache hot SKUs?
Redis cache-aside on GET /products/{sku}.