0Pricing
MongoDB Academy · 강의

요구 사항 분석 및 스키마 설계

학습자는 애플리케이션 요구 사항을 MongoDB 스키마로 변환하고, 포함 방식과 참조 방식 중 하나를 선택하여 설계 패턴을 적절히 적용합니다.

요구 사항 분석 및 스키마 설계은(는) CoddyKit의 무료 MongoDB Academy 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 MongoDB Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. MongoDB Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

The Capstone: Designing a Real Application

In this capstone lesson, you apply all the knowledge from the MongoDB track to design a production-ready application from scratch. We will build a multi-vendor e-commerce platform — a domain rich enough to exercise embedding vs referencing decisions, index planning, aggregation design, and security. The process starts with requirements analysis, which drives every schema decision that follows.

Step 1: Gather Functional Requirements

Begin by listing the application's core entities and operations. For our e-commerce platform: entities — Users, Vendors, Products, Orders, Reviews, Carts; operations — browse products by category, search by keyword, place orders, process payments, track shipping, and write reviews. Each operation maps to one or more MongoDB queries, and those queries drive schema decisions.

// Requirement analysis output (pseudocode spec)
const requirements = {
  reads: [
    'Get product by slug (very high frequency)',
    'List products by category + sort/filter (high frequency)',
    'Search products by keyword (high frequency)',
    'Get order history for a user (medium frequency)',
    'Get order detail (medium frequency)'
  ],
  writes: [
    'Create order (medium frequency)',
    'Update order status (medium frequency)',
    'Add product review (low frequency)',
    'Update product inventory (high frequency)'
  ]
}

Step 2: Identify Access Patterns

Access patterns are the specific queries your application will run. Document them precisely before designing the schema — the schema should serve the queries, not the other way around. For each pattern, record: the filter fields, sort fields, projected fields, and estimated frequency. High-frequency patterns drive indexing and embedding decisions. Low-frequency patterns can tolerate joins or aggregation pipeline overhead.

// Access pattern register
const accessPatterns = [
  {
    name: 'Product page',
    filter: { slug: 1 },
    projection: 'all except internal fields',
    frequency: 'very high',
    decision: 'index on slug; embed top 5 reviews'
  },
  {
    name: 'Category listing',
    filter: { categoryId: 1, price: 1 },
    sort: { price: 1, createdAt: -1 },
    frequency: 'high',
    decision: 'compound index { categoryId, price, createdAt }'
  }
]

Designing the Products Collection

The product document is the most frequently read document in the system. Apply the patterns learned: Computed Pattern for pre-computed stats (avgRating, reviewCount); Subset Pattern for embedding only the top 5 reviews; Extended Reference for embedding the vendor's name and logo alongside vendorId. This eliminates joins for 95% of product page renders.

// Product document schema (simplified)
{
  _id: ObjectId(),
  slug: 'wireless-headphones-pro',
  name: 'Wireless Headphones Pro',
  categoryId: ObjectId(),
  vendor: {
    _id: ObjectId(),         // reference for updates
    name: 'AudioTech Ltd',   // Extended Reference
    logoUrl: '...'           // Extended Reference
  },
  price: 149.99,
  stock: 234,
  avgRating: 4.3,           // Computed Pattern
  reviewCount: 892,          // Computed Pattern
  topReviews: [ /* 5 most recent */ ],  // Subset Pattern
  tags: ['audio', 'wireless', 'headphones'],
  schema_version: 1
}

Designing the Orders Collection

Orders are a classic snapshot document: they capture the state of prices and addresses at purchase time, not the current state. Embed the full shipping address (not a reference to the user's current address), the product snapshot (name, price, image at purchase time), and the vendor name. This ensures orders remain accurate even if prices change or vendors update their profiles.

// Order document schema
{
  _id: ObjectId(),
  userId: ObjectId(),
  status: 'processing',  // 'pending', 'processing', 'shipped', 'delivered', 'refunded'
  createdAt: new Date(),
  shippingAddress: {
    name: 'Alice Smith',
    street: '42 Elm St',
    city: 'Istanbul',
    country: 'TR'
  },
  items: [
    {
      productId: ObjectId(),     // reference for linking
      slug: 'wireless-...',
      name: 'Wireless Headphones Pro',  // snapshot
      price: 149.99,             // price at purchase time
      quantity: 1,
      imageUrl: '...'
    }
  ],
  subtotal: 149.99,
  tax: 27.00,
  total: 176.99
}

Applying the Schema Design Decision Framework

For each relationship, apply the decision checklist: How often is it accessed together? (embed if always, reference if rarely); How often does the referenced data change? (embed if rarely, reference if frequently); Will the embedded array grow without bound? (reference if yes, embed if bounded); Is the data queried independently? (separate collection if yes, embed if always accessed via parent).

// Decision table for our e-commerce schema
const decisions = [
  { entity: 'Order items',       decision: 'embed',     reason: 'always fetched with order; price snapshot required' },
  { entity: 'Shipping address',  decision: 'embed',     reason: 'snapshot at purchase time; changes do not affect order' },
  { entity: 'Product reviews',   decision: 'separate + subset', reason: 'grows unbounded; top-5 subset in product doc' },
  { entity: 'Vendor details',    decision: 'extended ref', reason: 'name/logo read on every product page; changes rarely' },
  { entity: 'Category tree',     decision: 'separate',  reason: 'queried independently; used for breadcrumbs' }
]

Schema Validation for Critical Collections

Add JSON Schema validators to the products and orders collections to prevent malformed documents from corrupting your data. Enforce required fields (price must be a positive number, status must be one of the valid enum values) and set validationAction: 'error' to reject invalid writes immediately rather than warn.

db.runCommand({
  collMod: 'orders',
  validator: {
    $jsonSchema: {
      bsonType: 'object',
      required: ['userId', 'status', 'items', 'total', 'createdAt'],
      properties: {
        status: {
          bsonType: 'string',
          enum: ['pending', 'processing', 'shipped', 'delivered', 'refunded']
        },
        total: { bsonType: 'double', minimum: 0 },
        items: { bsonType: 'array', minItems: 1 }
      }
    }
  },
  validationAction: 'error'
})

Planning the Index Set

Define indexes for every high-frequency access pattern. Use the ESR rule (Equality → Sort → Range) for compound indexes. Create indexes only for queries with high frequency — unused indexes waste write performance and RAM. Document each index with its purpose so the team can prune redundant ones as access patterns evolve.

// Products collection indexes
db.products.createIndex({ slug: 1 }, { unique: true })  // product page lookup
db.products.createIndex({ categoryId: 1, price: 1, _id: 1 })  // category listing + keyset pagination
db.products.createIndex({ tags: 1 })  // tag filter
db.products.createIndex({ 'vendor._id': 1 })  // vendor store page

// Orders collection indexes
db.orders.createIndex({ userId: 1, createdAt: -1 })  // user order history
db.orders.createIndex({ status: 1, createdAt: 1 })   // fulfillment queue

Handling Concurrent Inventory Updates

A critical challenge in e-commerce is preventing overselling: two users should not both be able to purchase the last item in stock. Use MongoDB's atomic findOneAndUpdate with a stock: { $gt: 0 } guard condition. The update only succeeds if stock is available, and the decrement is atomic — no race condition possible.

// Atomically reserve stock — returns null if out of stock
const product = await db.collection('products').findOneAndUpdate(
  { _id: productId, stock: { $gte: quantity } },  // guard: enough stock
  { $inc: { stock: -quantity } },
  { returnDocument: 'after', projection: { stock: 1, name: 1, price: 1 } }
)

if (!product) {
  throw new Error('Insufficient stock')
}
// Proceed to create order with product snapshot

Aggregation Pipeline for Reporting

Design a sales summary aggregation pipeline that reports revenue by vendor for the last 30 days. This is a classic case where the aggregation pipeline replaces complex application-side computation. The pipeline matches recent orders, unwinds items, groups by vendor, and sorts by total revenue.

// Revenue by vendor, last 30 days
const since = new Date(Date.now() - 30 * 86400000)

db.orders.aggregate([
  { $match: { status: 'delivered', createdAt: { $gte: since } } },
  { $unwind: '$items' },
  {
    $group: {
      _id: '$items.vendorId',
      totalRevenue: { $sum: { $multiply: ['$items.price', '$items.quantity'] } },
      orderCount: { $addToSet: '$_id' }
    }
  },
  { $addFields: { orderCount: { $size: '$orderCount' } } },
  { $sort: { totalRevenue: -1 } },
  { $limit: 20 }
])

Testing the Schema With explain()

Before going to production, validate every critical query with explain('executionStats'). Confirm that all high-frequency queries show IXSCAN (not COLLSCAN) in the winning plan, docsExamined is close to nReturned, and totalKeysExamined is reasonable. Any query showing COLLSCAN or a high docsExamined/nReturned ratio needs an index.

// Validate the category listing query
const stats = db.products.find(
  { categoryId: ObjectId('...'), price: { $lte: 200 } }
).sort({ price: 1 }).explain('executionStats')

const plan = stats.executionStats
console.log('Stage:', plan.executionStages.inputStage.stage)  // should be IXSCAN
console.log('Keys examined:', plan.totalKeysExamined)          // should be small
console.log('Docs examined:', plan.totalDocsExamined)          // should equal nReturned
console.log('Docs returned:', plan.nReturned)

Quick Check

Test your understanding of MongoDB & NoSQL Databases concepts from this lesson.

Lesson Recap

In this lesson you learned: requirements analysis and access pattern documentation come before schema design — the schema serves the queries, not the other way around, a good product document combines Extended Reference, Computed Pattern, and Subset Pattern to eliminate joins on the hot read path, and atomic findOneAndUpdate with guard conditions prevents overselling without requiring transactions. Next up we define the index strategy and validate each index with explain().

자주 묻는 질문

“요구 사항 분석 및 스키마 설계” 강의는 무료인가요?

네 — “요구 사항 분석 및 스키마 설계” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 MongoDB Academy 강의 전체를 잠금 해제할 수 있습니다. MongoDB Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“요구 사항 분석 및 스키마 설계”에서 뭘 배우나요?

학습자는 애플리케이션 요구 사항을 MongoDB 스키마로 변환하고, 포함 방식과 참조 방식 중 하나를 선택하여 설계 패턴을 적절히 적용합니다. 브라우저에서 직접 실행하는 실습 코드로 MongoDB Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

MongoDB Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 MongoDB Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.

“요구 사항 분석 및 스키마 설계” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 MongoDB Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 MongoDB Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 요구 사항 분석 및 스키마 설계
  2. 인덱스 전략 및 쿼리 플래너 검증
  3. 확장 계획: 복제 세트에서 샤딩 클러스터까지
  4. 보안 강화 및 운영 환경 점검 목록
← MongoDB Academy(으)로 돌아가기