Problems API ์„œ๋น„์Šค

1. ๊ฐœ์š”

Problems API ์„œ๋น„์Šค๋Š” DynamoDB ํ…Œ์ด๋ธ”์— ์ €์žฅ๋œ ์ฝ”๋”ฉ ๋ฌธ์ œ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ธฐ ์œ„ํ•œ HTTP ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ์„œ๋น„์Šค๋ฅผ ํ†ตํ•ด โ€œ์™„๋ฃŒ๋œ(completed)โ€ ์ƒํƒœ์˜ ๋ชจ๋“  ๋ฌธ์ œ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ค๊ณ (ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋ฐ ์ƒ์„ฑ์ž๋ณ„ ํ•„ํ„ฐ๋ง ์˜ต์…˜ ํฌํ•จ), ํŠน์ • ๋ฌธ์ œ ID๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น ๋ฌธ์ œ์˜ ์ƒ์„ธ ์ •๋ณด๋ฅผ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์„œ๋น„์Šค๋Š” AWS Lambda(์ปดํ“จํŒ…), API Gateway(์š”์ฒญ ๋ผ์šฐํŒ…), DynamoDB(๋ฐ์ดํ„ฐ ์ €์žฅ)๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์„œ๋ฒ„๋ฆฌ์Šค(Serverless) ๋ฐฉ์‹์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ธํ”„๋ผ๋Š” Terraform์„ ํ†ตํ•ด ์ฝ”๋“œ๋กœ ๊ด€๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

2. ๊ธฐ๋Šฅ

  • ๋ฌธ์ œ ๋ชฉ๋ก ์กฐํšŒ: ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ์ง€์›ํ•˜๋Š” ๋ฌธ์ œ ๋ชฉ๋ก์„ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.
    • creatorId๋ฅผ ์‚ฌ์šฉํ•œ ํ•„ํ„ฐ๋ง์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
    • ์ƒ์„ฑ ๋‚ ์งœ(createdAt)๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์˜ค๋ฆ„์ฐจ์ˆœ ๋˜๋Š” ๋‚ด๋ฆผ์ฐจ์ˆœ ์ •๋ ฌ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
    • generationStatus = "completed" ์ƒํƒœ์ธ ๋ฌธ์ œ๋งŒ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • ID๋กœ ๋ฌธ์ œ ์กฐํšŒ: ๊ณ ์œ ํ•œ problemId๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹จ์ผ ๋ฌธ์ œ์˜ ์ „์ฒด ์ƒ์„ธ ์ •๋ณด๋ฅผ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.
  • CORS ์ง€์›: ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ฐ˜ ํด๋ผ์ด์–ธํŠธ ์ ‘๊ทผ์„ ์œ„ํ•œ CORS(Cross-Origin Resource Sharing) ํ—ค๋”๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
  • ์„œ๋ฒ„๋ฆฌ์Šค ์•„ํ‚คํ…์ฒ˜: AWS ๊ด€๋ฆฌํ˜• ์„œ๋น„์Šค๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํ™•์žฅ์„ฑ์„ ํ™•๋ณดํ•˜๊ณ  ์šด์˜ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ค„์ž…๋‹ˆ๋‹ค.
  • Infrastructure as Code (IaC): ๋ชจ๋“  AWS ๋ฆฌ์†Œ์Šค๋Š” Terraform์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋“œ๋กœ ์ •์˜ํ•˜๊ณ  ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

3. API ์—”๋“œํฌ์ธํŠธ

API์˜ ๊ธฐ๋ณธ URL์€ API Gateway ๋ฐฐํฌ์— ์˜ํ•ด ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค (์˜ˆ: https://{api-id}.execute-api.{region}.amazonaws.com/{stage}).

3.1. ๋ชจ๋“  ๋ฌธ์ œ ์กฐํšŒ (Get All Problems)

๋ฌธ์ œ ๋ชฉ๋ก์„ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. ์ฃผ๋กœ generationStatus = "completed" ์ƒํƒœ์ธ ๋ฌธ์ œ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

  • ๋ฉ”์„œ๋“œ: GET
  • ๊ฒฝ๋กœ: /problems
  • ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ:
    • creatorId (์„ ํƒ ์‚ฌํ•ญ, ๋ฌธ์ž์—ด): ๋ฌธ์ œ ์ƒ์„ฑ์ž์˜ ID๋กœ ๋ฌธ์ œ๋ฅผ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค. ์ด ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์ œ๊ณต๋˜๋ฉด CreatorIdCreatedAtGSI๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, ์ถ”๊ฐ€์ ์œผ๋กœ generationStatus = "completed" ์กฐ๊ฑด์œผ๋กœ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค.
    • pageSize (์„ ํƒ ์‚ฌํ•ญ, ์ˆซ์ž, ๊ธฐ๋ณธ๊ฐ’: 20): ํŽ˜์ด์ง€๋‹น ๋ฐ˜ํ™˜ํ•  ์ตœ๋Œ€ ๋ฌธ์ œ ์ˆ˜์ž…๋‹ˆ๋‹ค.
    • sortOrder (์„ ํƒ ์‚ฌํ•ญ, ๋ฌธ์ž์—ด, ๊ธฐ๋ณธ๊ฐ’: DESC): createdAt ๊ธฐ์ค€ ์ •๋ ฌ ์ˆœ์„œ์ž…๋‹ˆ๋‹ค. ASC ๋˜๋Š” DESC ๊ฐ’์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • lastEvaluatedKey (์„ ํƒ ์‚ฌํ•ญ, ๋ฌธ์ž์—ด): ์ด์ „ ์‘๋‹ต์—์„œ ๋ฐ›์€ LastEvaluatedKey ๊ฐ’์„ URL ์ธ์ฝ”๋”ฉ๋œ JSON ๋ฌธ์ž์—ด ํ˜•ํƒœ๋กœ ์ „๋‹ฌํ•˜๋ฉฐ, ํŽ˜์ด์ง€๋„ค์ด์…˜์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ์„ฑ๊ณต ์‘๋‹ต (200 OK):
    {
      "items": [
        {
          "problemId": "uuid-problem-1",
          "title": "Problem Title 1",
          "title_translated": "๋ฒˆ์—ญ๋œ ๋ฌธ์ œ ์ œ๋ชฉ 1",
          "difficulty": "Medium",
          "algorithmType": "Dynamic Programming",
          "createdAt": "2023-10-26T10:00:00.000Z",
          "creatorId": "user-abc",
          "author": "Author Name",
          "generationStatus": "completed"
        }
        // ... ์ถ”๊ฐ€ ๋ฌธ์ œ ํ•ญ๋ชฉ๋“ค
      ],
      "lastEvaluatedKey": "encoded_json_string_or_null", // ๋‹ค์Œ ํŽ˜์ด์ง€ ์กฐํšŒ๋ฅผ ์œ„ํ•œ ํ‚ค (์—†์œผ๋ฉด null)
      "count": 20, // ํ˜„์žฌ ์‘๋‹ต์— ํฌํ•จ๋œ ํ•ญ๋ชฉ ์ˆ˜
      "scannedCount": 25 // ํ•„ํ„ฐ๋ง ์ „ ์Šค์บ”๋œ ํ•ญ๋ชฉ ์ˆ˜ (ํ•ด๋‹น๋˜๋Š” ๊ฒฝ์šฐ)
    }
    
    • items ๋ฐฐ์—ด์€ Lambda ํ•จ์ˆ˜ ๋‚ด ProjectionExpression์— ์ •์˜๋œ ๋ฌธ์ œ ์†์„ฑ๋“ค์˜ ์ผ๋ถ€๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
  • ์˜ค๋ฅ˜ ์‘๋‹ต:
    • 400 Bad Request: lastEvaluatedKey ํ˜•์‹์ด ์ž˜๋ชป๋œ ๊ฒฝ์šฐ.
    • 500 Internal Server Error: PROBLEMS_TABLE_NAME ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์„œ๋ฒ„ ์ธก ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ.

3.2. ID๋กœ ๋ฌธ์ œ ์กฐํšŒ (Get Problem by ID)

ํŠน์ • ๋ฌธ์ œ์˜ ์ „์ฒด ์ƒ์„ธ ์ •๋ณด๋ฅผ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.

  • ๋ฉ”์„œ๋“œ: GET
  • ๊ฒฝ๋กœ: /problems/{problemId}
  • ๊ฒฝ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ:
    • problemId (ํ•„์ˆ˜, ๋ฌธ์ž์—ด): ๋ฌธ์ œ์˜ ๊ณ ์œ  ์‹๋ณ„์ž์ž…๋‹ˆ๋‹ค.
  • ์„ฑ๊ณต ์‘๋‹ต (200 OK):
    {
      "problemId": "uuid-problem-1",
      "userPrompt": "Generate a hard problem about graphs.",
      "difficulty": "Hard",
      "language": "python",
      "targetLanguage": "ko",
      "createdAt": "2023-10-26T10:00:00.000Z",
      "completedAt": "2023-10-26T10:05:00.000Z",
      "generationStatus": "completed",
      "errorMessage": null,
      "title": "Graph Traversal Challenge",
      "title_translated": "๊ทธ๋ž˜ํ”„ ํƒ์ƒ‰ ์ฑŒ๋ฆฐ์ง€",
      "description": "Detailed problem description...",
      "description_translated": "์ƒ์„ธ ๋ฒˆ์—ญ๋œ ๋ฌธ์ œ ์„ค๋ช…...",
      "intent": "User's intent for the problem...",
      "finalTestCases": "[{\"input\": ...}, ...]",
      "validatedSolutionCode": "def solution(...): ...",
      "startCode": "def solution(...): \n  # Your code here\n  pass",
      "constraints": "Problem constraints...",
      // ... ProblemDetail ์ธํ„ฐํŽ˜์ด์Šค์— ์ •์˜๋œ ๋‹ค๋ฅธ ์†์„ฑ๋“ค
      "creatorId": "user-xyz",
      "author": "Author Name"
    }
    
  • ์˜ค๋ฅ˜ ์‘๋‹ต:
    • 400 Bad Request: problemId๊ฐ€ ๋ˆ„๋ฝ๋œ ๊ฒฝ์šฐ.
    • 404 Not Found: ์ฃผ์–ด์ง„ problemId์— ํ•ด๋‹นํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ.
    • 500 Internal Server Error: PROBLEMS_TABLE_NAME ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์„œ๋ฒ„ ์ธก ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ.

3.3. CORS ํ”„๋ฆฌํ”Œ๋ผ์ดํŠธ (CORS Preflight)

/problems ๋ฐ /problems/{problemId} ๊ฒฝ๋กœ๋Š” ๋ชจ๋‘ CORS ํ”„๋ฆฌํ”Œ๋ผ์ดํŠธ ๊ฒ€์‚ฌ๋ฅผ ์œ„ํ•ด OPTIONS ์š”์ฒญ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. API Gateway๋Š” ์ด๋Ÿฌํ•œ ์š”์ฒญ์— ๋Œ€ํ•ด MOCK ํ†ตํ•ฉ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์œผ๋ฉฐ, Lambda ํ•จ์ˆ˜ ๋˜ํ•œ ๋ช…์‹œ์ ์œผ๋กœ OPTIONS ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜์—ฌ ์ ์ ˆํ•œ CORS ํ—ค๋”๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

  • ๋ฉ”์„œ๋“œ: OPTIONS
  • ๊ฒฝ๋กœ: /problems ๋˜๋Š” /problems/{problemId}
  • ์„ฑ๊ณต ์‘๋‹ต (200 OK):
    • ํ—ค๋”:
      • Access-Control-Allow-Origin: * (์ฐธ๊ณ : ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ํŠน์ • ๋„๋ฉ”์ธ์œผ๋กœ ์ œํ•œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.)
      • Access-Control-Allow-Methods: GET, OPTIONS
      • Access-Control-Allow-Headers: Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token
    • ๋ณธ๋ฌธ: {"message": "CORS preflight check successful"} (Lambda์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ) ๋˜๋Š” ๋น„์–ด ์žˆ์Œ (API Gateway MOCK ํ†ตํ•ฉ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ)

4. ๊ธฐ์ˆ  ์Šคํƒ

  • AWS Lambda: Node.js 20.x ๋Ÿฐํƒ€์ž„ (๋ฐฑ์—”๋“œ ๋กœ์ง ์ฒ˜๋ฆฌ).
    • @aws-sdk/client-dynamodb: DynamoDB ํด๋ผ์ด์–ธํŠธ๋ฅผ ์œ„ํ•œ AWS SDK v3.
    • @aws-sdk/lib-dynamodb: ์†์‰ฌ์šด JSON ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ AWS SDK v3 DynamoDB Document Client.
  • Amazon API Gateway: HTTP ์—”๋“œํฌ์ธํŠธ ๊ด€๋ฆฌ, ์š”์ฒญ ๋ผ์šฐํŒ… ๋ฐ CORS ์ฒ˜๋ฆฌ.
  • Amazon DynamoDB: ๋ฌธ์ œ ๋ฐ์ดํ„ฐ ์ €์žฅ์„ ์œ„ํ•œ NoSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค. ํ…Œ์ด๋ธ” ์ž์ฒด๋Š” problem-generator-v3 ๋ชจ๋“ˆ์—์„œ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • Terraform: AWS ๋ฆฌ์†Œ์Šค ํ”„๋กœ๋น„์ €๋‹ ๋ฐ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ Infrastructure as Code (IaC) ๋„๊ตฌ.
  • AWS IAM: Lambda ๋ฐ API Gateway์˜ ๊ถŒํ•œ ๋ฐ ์—ญํ•  ๊ด€๋ฆฌ.
  • AWS CloudWatch: ๋กœ๊น… ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง.

5. ๋ฐ์ดํ„ฐ ๋ชจ๋ธ (DynamoDB)

API๋Š” DynamoDB ํ…Œ์ด๋ธ”๊ณผ ์ƒํ˜ธ์ž‘์šฉํ•ฉ๋‹ˆ๋‹ค (ํ…Œ์ด๋ธ” ์ด๋ฆ„์€ PROBLEMS_TABLE_NAME ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•ด ์ œ๊ณต๋จ).

  • ํ…Œ์ด๋ธ” ์ด๋ฆ„: Terraform์—์„œ local.problems_table_name์œผ๋กœ ์ฐธ์กฐ๋˜๋ฉฐ, ์ด๋Š” problem-generator-v3 ๋ชจ๋“ˆ์˜ ์›๊ฒฉ ์ƒํƒœ(remote state)์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  • ๊ธฐ๋ณธ ํ‚ค (Primary Key):
    • problemId (๋ฌธ์ž์—ด): ํŒŒํ‹ฐ์…˜ ํ‚ค (Partition Key).
  • ์ด API์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ธ€๋กœ๋ฒŒ ๋ณด์กฐ ์ธ๋ฑ์Šค (Global Secondary Indexes, GSIs):
    1. CompletedProblemsByCreatedAtGSI (getAllProblems.mjs์— ๋ช…๋ช…๋œ ์ด๋ฆ„)
      • ๋ชฉ์ : ์™„๋ฃŒ๋œ(completed) ๋ชจ๋“  ๋ฌธ์ œ๋ฅผ ์ƒ์„ฑ ๋‚ ์งœ ์ˆœ์œผ๋กœ ์กฐํšŒ.
      • ํŒŒํ‹ฐ์…˜ ํ‚ค (PK): generationStatus (๋ฌธ์ž์—ด)
      • ์ •๋ ฌ ํ‚ค (SK): createdAt (๋ฌธ์ž์—ด, ISO 8601 ํ˜•์‹)
      • ์ฟผ๋ฆฌ ๋กœ์ง: generationStatus = "completed", createdAt ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌ.
    2. CreatorIdCreatedAtGSI (getAllProblems.mjs์— ๋ช…๋ช…๋œ ์ด๋ฆ„)
      • ๋ชฉ์ : ํŠน์ • ์‚ฌ์šฉ์ž๊ฐ€ ์ƒ์„ฑํ•œ ๋ฌธ์ œ๋ฅผ ์ƒ์„ฑ ๋‚ ์งœ ์ˆœ์œผ๋กœ ์กฐํšŒ.
      • ํŒŒํ‹ฐ์…˜ ํ‚ค (PK): creatorId (๋ฌธ์ž์—ด)
      • ์ •๋ ฌ ํ‚ค (SK): createdAt (๋ฌธ์ž์—ด, ISO 8601 ํ˜•์‹)
      • ์ฟผ๋ฆฌ ๋กœ์ง: creatorId = :creatorIdVal, createdAt ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌ. ์ถ”๊ฐ€์ ์œผ๋กœ generationStatus = "completed" ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š” ํ•ญ๋ชฉ๋งŒ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค.
  • ์ฃผ์š” ์†์„ฑ (์ผ๋ถ€, ์ „์ฒด ๋ชฉ๋ก์€ problemApi.ts์˜ ProblemSummary ๋ฐ ProblemDetail ํƒ€์ž… ์ •์˜ ์ฐธ์กฐ):
    • problemId: String (PK) - ๋ฌธ์ œ์˜ ๊ณ ์œ  ID.
    • title: String - ๋ฌธ์ œ ์ œ๋ชฉ.
    • title_translated: String (์„ ํƒ ์‚ฌํ•ญ) - ๋ฒˆ์—ญ๋œ ๋ฌธ์ œ ์ œ๋ชฉ.
    • description: String - ๋ฌธ์ œ์˜ ์ „์ฒด ์„ค๋ช….
    • description_translated: String (์„ ํƒ ์‚ฌํ•ญ) - ๋ฒˆ์—ญ๋œ ๋ฌธ์ œ ์„ค๋ช….
    • difficulty: String - ๋ฌธ์ œ ๋‚œ์ด๋„ (์˜ˆ: โ€œEasyโ€, โ€œMediumโ€, โ€œHardโ€).
    • algorithmType: String (์„ ํƒ ์‚ฌํ•ญ) - ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์œ ํ˜• (์˜ˆ: โ€œArraysโ€, โ€œDynamic Programmingโ€).
    • createdAt: String (ISO 8601 ํ˜•์‹) - ์ƒ์„ฑ ํƒ€์ž„์Šคํƒฌํ”„.
    • creatorId: String (์„ ํƒ ์‚ฌํ•ญ) - ๋ฌธ์ œ ์ƒ์„ฑ์„ ์š”์ฒญํ•œ ์‚ฌ์šฉ์ž ID.
    • author: String (์„ ํƒ ์‚ฌํ•ญ) - ๋ฌธ์ œ ์ถœ์ œ์ž (AI ๋ชจ๋ธ ์ด๋ฆ„ ๋˜๋Š” ์‚ฌ์šฉ์ž).
    • generationStatus: String - ๋ฌธ์ œ ์ƒ์„ฑ ์ƒํƒœ (์˜ˆ: โ€œpendingโ€, โ€œcompletedโ€, โ€œfailedโ€). ์ด API๋Š” ์ฃผ๋กœ โ€œcompletedโ€ ์ƒํƒœ์˜ ๋ฌธ์ œ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
    • finalTestCases: String (JSON ๋ฐฐ์—ด ํ˜•์‹) - ๋ฌธ์ œ์˜ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค.
    • startCode: String (์„ ํƒ ์‚ฌํ•ญ) - ์‚ฌ์šฉ์ž๋ฅผ ์œ„ํ•œ ๊ธฐ๋ณธ ์ œ๊ณต ์ฝ”๋“œ.
    • validatedSolutionCode: String (์„ ํƒ ์‚ฌํ•ญ) - AI๊ฐ€ ์ƒ์„ฑํ•œ ์†”๋ฃจ์…˜ ์ฝ”๋“œ.
    • language: String - ๋ฌธ์ œ์˜ ์ฃผ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด (์˜ˆ: โ€œpythonโ€, โ€œjavascriptโ€).

6. Lambda ํ•จ์ˆ˜

API๋Š” capstone-2025-04/backend/lambdas/problems-api/ ๋””๋ ‰ํ† ๋ฆฌ์— ์ •์˜๋œ ๋‘ ๊ฐœ์˜ ์ฃผ์š” Lambda ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:

  1. getAllProblems.mjs
    • Terraform ๋ฆฌ์†Œ์Šค ์ด๋ฆ„: aws_lambda_function.get_all_problems
    • AWS์—์„œ์˜ ํ•จ์ˆ˜ ์ด๋ฆ„: ${var.project_name}-getAllProblems-${var.environment}
    • ํ•ธ๋“ค๋Ÿฌ: getAllProblems.handler
    • ๋ชฉ์ : GET /problems ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. GSIs๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ DynamoDB์—์„œ ๋ฌธ์ œ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
    • ์ฃผ์š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜:
      • PROBLEMS_TABLE_NAME: DynamoDB ํ…Œ์ด๋ธ” ์ด๋ฆ„.
      • AWS_NODEJS_CONNECTION_REUSE_ENABLED=1: SDK ์—ฐ๊ฒฐ ์žฌ์‚ฌ์šฉ ์ตœ์ ํ™”.
  2. getProblemById.mjs
    • Terraform ๋ฆฌ์†Œ์Šค ์ด๋ฆ„: aws_lambda_function.get_problem_by_id
    • AWS์—์„œ์˜ ํ•จ์ˆ˜ ์ด๋ฆ„: ${var.project_name}-getProblemById-${var.environment}
    • ํ•ธ๋“ค๋Ÿฌ: getProblemById.handler
    • ๋ชฉ์ : GET /problems/{problemId} ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ํ‚ค์— ๋Œ€ํ•ด GetItem ์ž‘์—…์„ ์‚ฌ์šฉํ•˜์—ฌ DynamoDB์—์„œ ๋‹จ์ผ ๋ฌธ์ œ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
    • ์ฃผ์š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜:
      • PROBLEMS_TABLE_NAME: DynamoDB ํ…Œ์ด๋ธ” ์ด๋ฆ„.
      • AWS_NODEJS_CONNECTION_REUSE_ENABLED=1: SDK ์—ฐ๊ฒฐ ์žฌ์‚ฌ์šฉ ์ตœ์ ํ™”.

์ฐธ๊ณ : getProblems.mjs ํŒŒ์ผ์€ Lambda ์†Œ์Šค ๋””๋ ‰ํ† ๋ฆฌ์— ์กด์žฌํ•˜์ง€๋งŒ, ํ˜„์žฌ lambdas.tf์˜ Terraform ๊ตฌ์„ฑ์— ์˜ํ•ด ๋ฐฐํฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Terraform ๊ตฌ์„ฑ์€ ๊ฐ ๊ฒฝ๋กœ์— ๋Œ€ํ•ด getAllProblems.mjs ๋ฐ getProblemById.mjs๋ฅผ ๋ฐฐํฌํ•ฉ๋‹ˆ๋‹ค.

7. ์ธํ”„๋ผ ๋ฐ ๋ฐฐํฌ (Terraform)

์ด API์˜ ์ธํ”„๋ผ๋Š” capstone-2025-04/infrastructure/problems-api/ ๋””๋ ‰ํ† ๋ฆฌ์— ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์‚ฌ์ „ ์ค€๋น„ ์‚ฌํ•ญ

  • Terraform CLI (์˜ˆ: v1.x ๋ฒ„์ „)
  • ์ ์ ˆํ•œ ์ž๊ฒฉ ์ฆ๋ช… ๋ฐ ๊ธฐ๋ณธ ๋ฆฌ์ „์œผ๋กœ ๊ตฌ์„ฑ๋œ AWS CLI.
  • Terraform ์›๊ฒฉ ์ƒํƒœ ๋ฐฑ์—”๋“œ(S3 bucket ๋ฐ DynamoDB table) (backend.tf ํŒŒ์ผ์— ๊ตฌ์„ฑ๋จ). ์ด๋Š” ์‚ฌ์ „์— ์„ค์ •๋˜์–ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค (์˜ˆ: backend-setup ๋ชจ๋“ˆ์— ์˜ํ•ด).
  • problem-generator-v3 Terraform ๋ชจ๋“ˆ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ ์šฉ(apply)๋˜์–ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด API๋Š” ํ•ด๋‹น ๋ชจ๋“ˆ์˜ ์ƒํƒœ๋ฅผ ์ฝ์–ด DynamoDB ํ…Œ์ด๋ธ” ์ด๋ฆ„๊ณผ ARN์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

๋ฐฐํฌ ๋‹จ๊ณ„

  1. Terraform ๋ชจ๋“ˆ ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค:
    cd capstone-2025-04/infrastructure/problems-api
    
  2. Terraform์„ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค:
    terraform init
    
  3. (์„ ํƒ ์‚ฌํ•ญ) ์‹คํ–‰ ๊ณ„ํš์„ ๊ฒ€ํ† ํ•ฉ๋‹ˆ๋‹ค:
    terraform plan -var-file="dev.tfvars" # ๋˜๋Š” ํ•ด๋‹น ํ™˜๊ฒฝ์˜ .tfvars ํŒŒ์ผ
    

    aws_region, project_name, environment์™€ ๊ฐ™์€ ๋ณ€์ˆ˜๋ฅผ ์œ„ํ•ด .tfvars ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  4. Terraform ๊ตฌ์„ฑ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค:
    terraform apply -var-file="dev.tfvars" # ๋˜๋Š” ํ•ด๋‹น ํ™˜๊ฒฝ์˜ .tfvars ํŒŒ์ผ
    

    ํ”„๋กฌํ”„ํŠธ๊ฐ€ ๋‚˜ํƒ€๋‚˜๋ฉด yes๋ฅผ ์ž…๋ ฅํ•˜์—ฌ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์š” Terraform ํŒŒ์ผ

  • apigateway.tf: API Gateway REST API, ๋ฆฌ์†Œ์Šค, ๋ฉ”์„œ๋“œ, ํ†ตํ•ฉ ๋ฐ ๋ฐฐํฌ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  • lambdas.tf: AWS Lambda ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•˜๋ฉฐ, ์†Œ์Šค ์ฝ”๋“œ ํŒจํ‚ค์ง•(data "archive_file")์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
  • iam.tf: Lambda ์‹คํ–‰ ๋ฐ API Gateway ๋กœ๊น…์„ ์œ„ํ•œ IAM ์—ญํ•  ๋ฐ ์ •์ฑ…์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  • dynamodb.tf: problem-generator-v3 ๋ชจ๋“ˆ์—์„œ DynamoDB ํ…Œ์ด๋ธ” ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ terraform_remote_state ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.
  • variables.tf: ๋ชจ๋“ˆ์˜ ์ž…๋ ฅ ๋ณ€์ˆ˜ (์˜ˆ: aws_region, project_name, environment).
  • outputs.tf: ๋ฐฐํฌ ํ›„ ์ƒ์„ฑ๋˜๋Š” ์ถœ๋ ฅ ๊ฐ’ (์˜ˆ: problems_api_invoke_url).
  • providers.tf: AWS ๊ณต๊ธ‰์ž ๊ตฌ์„ฑ์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
  • backend.tf: Terraform S3 ์›๊ฒฉ ์ƒํƒœ ๋ฐฑ์—”๋“œ๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

Lambda ์ฝ”๋“œ ํŒจํ‚ค์ง•

lambdas.tf ํŒŒ์ผ์€ data "archive_file" ๋ธ”๋ก์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐœ๋ณ„ .mjs ํŒŒ์ผ์„ lambda_zips ํ•˜์œ„ ๋””๋ ‰ํ† ๋ฆฌ(lambdas.tf ํŒŒ์ผ ๊ธฐ์ค€ ์ƒ๋Œ€ ๊ฒฝ๋กœ) ๋‚ด์˜ .zip ์•„์นด์ด๋ธŒ(์˜ˆ: getAllProblems.zip, getProblemById.zip)๋กœ ํŒจํ‚ค์ง•ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ž‘์—…์€ terraform plan/apply ๋‹จ๊ณ„์—์„œ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์ƒ์„ฑ๋œ zip ํŒŒ์ผ๋“ค์€ Lambda ํ•จ์ˆ˜์— ์—…๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค.

8. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ (Lambda)

  • PROBLEMS_TABLE_NAME: (ํ•„์ˆ˜) ๋ฌธ์ œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” DynamoDB ํ…Œ์ด๋ธ”์˜ ์ด๋ฆ„์ž…๋‹ˆ๋‹ค. ์ด ๊ฐ’์€ Terraform (local.problems_table_name)์œผ๋กœ๋ถ€ํ„ฐ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.
  • AWS_NODEJS_CONNECTION_REUSE_ENABLED: AWS SDK์˜ TCP ์—ฐ๊ฒฐ ์žฌ์‚ฌ์šฉ์„ ํ™œ์„ฑํ™”ํ•˜์—ฌ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•ด 1๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค.

9. CORS ๊ตฌ์„ฑ

CORS๋Š” ๋‹ค์Œ ๋‘ ๊ฐ€์ง€ ์ˆ˜์ค€์—์„œ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค:

  1. API Gateway: /problems ๋ฐ /problems/{problemId} ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•ด OPTIONS ๋ฉ”์„œ๋“œ๊ฐ€ MOCK ํ†ตํ•ฉ์œผ๋กœ ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํ†ตํ•ฉ์€ ํ•„์š”ํ•œ Access-Control-Allow-* ํ—ค๋”๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  2. Lambda ํ•จ์ˆ˜: ๊ฐ Lambda ํ•ธ๋“ค๋Ÿฌ๋Š” ๋ช…์‹œ์ ์œผ๋กœ event.httpMethod === "OPTIONS"๋ฅผ ํ™•์ธํ•˜๊ณ  CORS ํ—ค๋”์™€ ํ•จ๊ป˜ 200 ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” API Gateway MOCK ํ†ตํ•ฉ์ด OPTIONS์— ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ๋Œ€์ฒด ๋ฉ”์ปค๋‹ˆ์ฆ˜์œผ๋กœ ์ž‘๋™ํ•˜๊ฑฐ๋‚˜ ์ฃผ์š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Access-Control-Allow-Origin ํ—ค๋”๋Š” ํ˜„์žฌ * (๋ชจ๋“  ์˜ค๋ฆฌ์ง„ ํ—ˆ์šฉ)๋กœ ์„ค์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์ด ๊ฐ’์„ ํ”„๋ก ํŠธ์—”๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํŠน์ • ๋„๋ฉ”์ธ์œผ๋กœ ์ œํ•œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

10. ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ

  • Lambda ํ•จ์ˆ˜๋Š” ์ ์ ˆํ•œ HTTP ์ƒํƒœ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ JSON ํ˜•์‹์˜ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • ์ผ๋ฐ˜์ ์ธ ์˜ค๋ฅ˜ ์‘๋‹ต์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:
    • 400 Bad Request: ์ž˜๋ชป๋œ ์ž…๋ ฅ ๊ฐ’ (์˜ˆ: ํ˜•์‹์ด ์ž˜๋ชป๋œ lastEvaluatedKey).
    • 404 Not Found: ํŠน์ • ๋ฆฌ์†Œ์Šค (์˜ˆ: ID๋กœ ์กฐํšŒํ•œ ๋ฌธ์ œ)๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ.
    • 500 Internal Server Error: ์„œ๋ฒ„ ์ธก ๊ตฌ์„ฑ ๋ฌธ์ œ (์˜ˆ: PROBLEMS_TABLE_NAME ๋ˆ„๋ฝ) ๋˜๋Š” DynamoDB ์ž‘์—… ์ค‘ ์˜ˆ๊ธฐ์น˜ ์•Š์€ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ. ์˜ค๋ฅ˜ ์„ธ๋ถ€ ์ •๋ณด๋Š” CloudWatch Logs์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.
  • ํ”„๋ก ํŠธ์—”๋“œ problemApi.ts ํŒŒ์ผ์—๋Š” ์ด๋Ÿฌํ•œ ์‘๋‹ต์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ApiError ํด๋ž˜์Šค์™€ handleApiResponse ํ•จ์ˆ˜๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.