๐Ÿ“‹ ํ”„๋กœ์ ํŠธ ๋ฉ”ํƒ€ ์ •๋ณด

ํ•ญ๋ชฉ๋‚ด์šฉ
ํ”„๋กœ์ ํŠธ๋ช…๋ฝ€์‹œ๋ ˆ๊ธฐ (BBOSSIREGI)
๊ธฐ๊ฐ„2026.02.24 ~ 2026.03.19 (24์ผ)
ํŒ€์›4๋ช…
์Šคํ”„๋ฆฐํŠธ1์ฃผ ๋‹จ์œ„ (์ด 4์Šคํ”„๋ฆฐํŠธ)

์šฐ์„ ์ˆœ์œ„ ์ •์˜

๋ ˆ๋ฒจ์„ค๋ช…๊ธฐ์ค€
P0ํ•„์ˆ˜ (Blocker)์ด๊ฑฐ ์—†์œผ๋ฉด ๋‹ค์Œ ์ž‘์—… ๋ถˆ๊ฐ€
P1ํ•ต์‹ฌ (Must Have)MVP ํ•„์ˆ˜ ๊ธฐ๋Šฅ
P2์ค‘์š” (Should Have)์žˆ์œผ๋ฉด ์ข‹์Œ
P3์„ ํƒ (Nice to Have)์‹œ๊ฐ„ ๋˜๋ฉด

๐Ÿ—“๏ธ Sprint 1: ์ธํ”„๋ผ ๊ธฐ๋ฐ˜ ๊ตฌ์ถ• (Day 1-5)

Day 1 (2/24 ์›”) - ์„ค๊ณ„ ๋ฐ AWS ๊ธฐ๋ฐ˜

ID์šฐ์„ ์ˆœ์œ„ํƒœ์Šคํฌ๋‹ด๋‹น์‚ฐ์ถœ๋ฌผ์™„๋ฃŒ ๊ธฐ์ค€
S1-001P0AWS ๊ณ„์ • ๋ฐ IAM ์„ค์ •์ธํ”„๋ผIAM ์ •์ฑ… JSONAdmin, Developer, ReadOnly ์—ญํ•  ์ƒ์„ฑ ์™„๋ฃŒ
S1-002P0ERD ์„ค๊ณ„ ํ™•์ •๋ฐฑ์—”๋“œERD ๋‹ค์ด์–ด๊ทธ๋žจ์ตœ์†Œ 5๊ฐœ ํ…Œ์ด๋ธ”, ๊ด€๊ณ„ ์ •์˜ ์™„๋ฃŒ
S1-003P0์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜ ๋‹ค์ด์–ด๊ทธ๋žจ์ „์ฒดdraw.io ํŒŒ์ผ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ + ๋ฐ์ดํ„ฐ ํ๋ฆ„ ํ‘œ์‹œ
S1-004P0MVP API ๋ช…์„ธ ์ž‘์„ฑ๋ฐฑ์—”๋“œOpenAPI 3.0 Spec์ตœ์†Œ 10๊ฐœ ์—”๋“œํฌ์ธํŠธ ์ •์˜
S1-005P0Git ๋ ˆํฌ์ง€ํ† ๋ฆฌ ๊ตฌ์„ฑ์ „์ฒดGitHub Repomono-repo ๊ตฌ์กฐ, branch ์ „๋žต ๋ฌธ์„œํ™”

๐Ÿ“ S1-002: ERD ๋ช…์„ธ

-- ํ•„์ˆ˜ ํ…Œ์ด๋ธ” (์ตœ์†Œ ๊ตฌํ˜„)
users (id, email, password_hash, name, created_at)
products (id, name, description, price, image_url, created_at)
time_deals (id, product_id, deal_price, stock_quantity, reserved_quantity, start_at, end_at, status)
orders (id, user_id, time_deal_id, quantity, total_price, status, created_at)
order_events (id, order_id, event_type, payload, created_at)  -- ์ด๋ฒคํŠธ ์†Œ์‹ฑ์šฉ

๐Ÿ“ S1-004: API ๋ช…์„ธ (ํ•„์ˆ˜ 10๊ฐœ)

# ์ธ์ฆ (2๊ฐœ)
POST /api/v1/auth/register     # ํšŒ์›๊ฐ€์ž…
POST /api/v1/auth/login        # ๋กœ๊ทธ์ธ
 
# ์ƒํ’ˆ (3๊ฐœ)
GET  /api/v1/products          # ์ƒํ’ˆ ๋ชฉ๋ก
GET  /api/v1/products/:id      # ์ƒํ’ˆ ์ƒ์„ธ
POST /api/v1/admin/products    # ์ƒํ’ˆ ๋“ฑ๋ก (๊ด€๋ฆฌ์ž)
 
# ํƒ€์ž„๋”œ (3๊ฐœ)
GET  /api/v1/timedeals         # ํƒ€์ž„๋”œ ๋ชฉ๋ก
GET  /api/v1/timedeals/:id     # ํƒ€์ž„๋”œ ์ƒ์„ธ (์žฌ๊ณ  ํฌํ•จ)
POST /api/v1/admin/timedeals   # ํƒ€์ž„๋”œ ์ƒ์„ฑ (๊ด€๋ฆฌ์ž)
 
# ์ฃผ๋ฌธ (2๊ฐœ)
POST /api/v1/orders            # ์ฃผ๋ฌธ ์ƒ์„ฑ (์žฌ๊ณ  ์˜ˆ์•ฝ)
GET  /api/v1/orders/:id        # ์ฃผ๋ฌธ ์ƒํƒœ ์กฐํšŒ

Day 2 (2/25 ํ™”) - ๋„คํŠธ์›Œํฌ ๋ฐ DB ๊ตฌ์ถ•

ID์šฐ์„ ์ˆœ์œ„ํƒœ์Šคํฌ๋‹ด๋‹น์‚ฐ์ถœ๋ฌผ์™„๋ฃŒ ๊ธฐ์ค€
S1-006P0VPC ์ƒ์„ฑ์ธํ”„๋ผTerraform ์ฝ”๋“œCIDR: 10.0.0.0/16
S1-007P0์„œ๋ธŒ๋„ท ๊ตฌ์„ฑ์ธํ”„๋ผTerraform ์ฝ”๋“œPublic 2๊ฐœ, Private 2๊ฐœ (Multi-AZ)
S1-008P0NAT Gateway + IGW์ธํ”„๋ผTerraform ์ฝ”๋“œPrivate ์„œ๋ธŒ๋„ท ์ธํ„ฐ๋„ท ์ ‘๊ทผ ๊ฐ€๋Šฅ
S1-009P0๋ณด์•ˆ ๊ทธ๋ฃน ์„ค์ •์ธํ”„๋ผTerraform ์ฝ”๋“œALB, EKS, RDS ๋ณ„๋„ SG
S1-010P0RDS PostgreSQL ๊ตฌ์ถ•์ธํ”„๋ผTerraform ์ฝ”๋“œdb.t3.micro, Multi-AZ ๋น„ํ™œ์„ฑํ™” (๋น„์šฉ)
S1-011P0DB ์Šคํ‚ค๋งˆ ์ƒ์„ฑ๋ฐฑ์—”๋“œDDL SQL ํŒŒ์ผ๋ชจ๋“  ํ…Œ์ด๋ธ” + ์ธ๋ฑ์Šค ์ƒ์„ฑ ์™„๋ฃŒ

๐Ÿ“ S1-006~009: VPC ๋ช…์„ธ

# terraform/vpc.tf
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"
 
  name = "bbossiregi-vpc"
  cidr = "10.0.0.0/16"
 
  azs             = ["ap-northeast-2a", "ap-northeast-2c"]
  public_subnets  = ["10.0.1.0/24", "10.0.2.0/24"]
  private_subnets = ["10.0.11.0/24", "10.0.12.0/24"]
 
  enable_nat_gateway = true
  single_nat_gateway = true  # ๋น„์šฉ ์ ˆ๊ฐ
 
  tags = {
    Project     = "bbossiregi"
    Environment = "dev"
  }
}

๐Ÿ“ S1-009: ๋ณด์•ˆ ๊ทธ๋ฃน ๋ช…์„ธ

SG ์ด๋ฆ„InboundOutbound์šฉ๋„
sg-alb80, 443 from 0.0.0.0/0AllALB
sg-eksAll from sg-albAllEKS ๋…ธ๋“œ
sg-rds5432 from sg-eksNoneRDS

๐Ÿ“ S1-011: DDL ๋ช…์„ธ

-- migrations/001_init.sql
 
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    name VARCHAR(100) NOT NULL,
    role VARCHAR(20) DEFAULT 'user',  -- user, admin
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
 
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    price DECIMAL(10,2) NOT NULL,
    image_url VARCHAR(500),
    category VARCHAR(50),  -- food, toy, health, etc
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
 
CREATE TABLE time_deals (
    id SERIAL PRIMARY KEY,
    product_id INTEGER REFERENCES products(id),
    deal_price DECIMAL(10,2) NOT NULL,
    original_price DECIMAL(10,2) NOT NULL,
    stock_quantity INTEGER NOT NULL,
    reserved_quantity INTEGER DEFAULT 0,
    sold_quantity INTEGER DEFAULT 0,
    start_at TIMESTAMP NOT NULL,
    end_at TIMESTAMP NOT NULL,
    status VARCHAR(20) DEFAULT 'scheduled',  -- scheduled, active, ended, soldout
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    CONSTRAINT valid_stock CHECK (reserved_quantity >= 0),
    CONSTRAINT valid_sold CHECK (sold_quantity >= 0),
    CONSTRAINT valid_total CHECK (reserved_quantity + sold_quantity <= stock_quantity)
);
 
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    time_deal_id INTEGER REFERENCES time_deals(id),
    quantity INTEGER NOT NULL DEFAULT 1,
    unit_price DECIMAL(10,2) NOT NULL,
    total_price DECIMAL(10,2) NOT NULL,
    status VARCHAR(20) DEFAULT 'pending',  -- pending, reserved, confirmed, canceled, failed
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
 
CREATE TABLE order_events (
    id SERIAL PRIMARY KEY,
    order_id INTEGER REFERENCES orders(id),
    event_type VARCHAR(50) NOT NULL,  -- CREATED, STOCK_RESERVED, PAYMENT_COMPLETED, CONFIRMED, CANCELED
    payload JSONB,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
 
-- ์ธ๋ฑ์Šค
CREATE INDEX idx_time_deals_status ON time_deals(status);
CREATE INDEX idx_time_deals_start_at ON time_deals(start_at);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_order_events_order_id ON order_events(order_id);

Day 3 (2/26 ์ˆ˜) - ๋ฐฑ์—”๋“œ ํ•ต์‹ฌ API

ID์šฐ์„ ์ˆœ์œ„ํƒœ์Šคํฌ๋‹ด๋‹น์‚ฐ์ถœ๋ฌผ์™„๋ฃŒ ๊ธฐ์ค€
S1-012P0Go ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐํ™”๋ฐฑ์—”๋“œmain.goGin ํ”„๋ ˆ์ž„์›Œํฌ ์„ค์ • ์™„๋ฃŒ
S1-013P0DB ์—ฐ๊ฒฐ ์„ค์ •๋ฐฑ์—”๋“œdb/postgres.goConnection pool ์„ค์ •
S1-014P1์œ ์ € ์ธ์ฆ API๋ฐฑ์—”๋“œhandlers/auth.goJWT ํ† ํฐ ๋ฐœ๊ธ‰
S1-015P1์ƒํ’ˆ CRUD API๋ฐฑ์—”๋“œhandlers/product.go๋ชฉ๋ก/์ƒ์„ธ/๋“ฑ๋ก
S1-016P1ํƒ€์ž„๋”œ API๋ฐฑ์—”๋“œhandlers/timedeal.go๋ชฉ๋ก/์ƒ์„ธ/๋“ฑ๋ก
S1-017P0ํƒ€์ž„๋”œ ์Šค์ผ€์ค„๋ง ๋กœ์ง๋ฐฑ์—”๋“œservices/scheduler.go์ž๋™ ์ƒํƒœ ๋ณ€๊ฒฝ

๐Ÿ“ S1-014: ์ธ์ฆ API ๋ช…์„ธ

// POST /api/v1/auth/register
// Request
{
    "email": "user@example.com",
    "password": "password123",
    "name": "ํ™๊ธธ๋™"
}
// Response 201
{
    "id": 1,
    "email": "user@example.com",
    "name": "ํ™๊ธธ๋™",
    "created_at": "2026-02-26T10:00:00Z"
}
// Error 400: ์ด๋ฉ”์ผ ์ค‘๋ณต
// Error 400: ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์‹คํŒจ
 
// POST /api/v1/auth/login
// Request
{
    "email": "user@example.com",
    "password": "password123"
}
// Response 200
{
    "access_token": "eyJhbGciOiJIUzI1NiIs...",
    "token_type": "Bearer",
    "expires_in": 3600
}
// Error 401: ์ธ์ฆ ์‹คํŒจ

๐Ÿ“ S1-016: ํƒ€์ž„๋”œ API ๋ช…์„ธ

// GET /api/v1/timedeals
// Response 200
{
    "data": [
        {
            "id": 1,
            "product": {
                "id": 1,
                "name": "ํ”„๋ฆฌ๋ฏธ์—„ ์‚ฌ๋ฃŒ 3kg",
                "image_url": "https://..."
            },
            "deal_price": 29900,
            "original_price": 45000,
            "discount_rate": 33,
            "stock_quantity": 100,
            "available_quantity": 87,  // stock - reserved - sold
            "start_at": "2026-02-27T14:00:00Z",
            "end_at": "2026-02-27T15:00:00Z",
            "status": "scheduled"
        }
    ],
    "meta": {
        "total": 10,
        "page": 1,
        "per_page": 20
    }
}
 
// GET /api/v1/timedeals/:id
// Response 200 (์‹ค์‹œ๊ฐ„ ์žฌ๊ณ  ํฌํ•จ)
{
    "id": 1,
    "product": { ... },
    "deal_price": 29900,
    "stock_quantity": 100,
    "reserved_quantity": 10,
    "sold_quantity": 3,
    "available_quantity": 87,
    "start_at": "2026-02-27T14:00:00Z",
    "end_at": "2026-02-27T15:00:00Z",
    "status": "active",
    "remaining_seconds": 1823  // ๋‚จ์€ ์‹œ๊ฐ„ (์ดˆ)
}

๐Ÿ“ S1-017: ์Šค์ผ€์ค„๋Ÿฌ ๋กœ์ง ๋ช…์„ธ

// services/scheduler.go
 
// ๋งค ๋ถ„๋งˆ๋‹ค ์‹คํ–‰
func (s *Scheduler) Run() {
    ticker := time.NewTicker(1 * time.Minute)
    for range ticker.C {
        s.activateDeals()   // scheduled โ†’ active
        s.expireDeals()     // active โ†’ ended (์‹œ๊ฐ„ ๋งŒ๋ฃŒ)
        s.checkSoldOut()    // active โ†’ soldout (์žฌ๊ณ  ์†Œ์ง„)
    }
}
 
// ์ƒํƒœ ์ „์ด ๊ทœ์น™
// scheduled โ†’ active: start_at <= now AND now < end_at
// active โ†’ ended: now >= end_at
// active โ†’ soldout: available_quantity <= 0

Day 4 (2/27 ๋ชฉ) - ์ฃผ๋ฌธ ๋ฐ ์ปจํ…Œ์ด๋„ˆํ™”

ID์šฐ์„ ์ˆœ์œ„ํƒœ์Šคํฌ๋‹ด๋‹น์‚ฐ์ถœ๋ฌผ์™„๋ฃŒ ๊ธฐ์ค€
S1-018P1์ฃผ๋ฌธ ์ƒ์„ฑ API (์‚ฌ๊ฐ€ ํŒจํ„ด)๋ฐฑ์—”๋“œhandlers/order.goReserve โ†’ Confirm ํ”Œ๋กœ์šฐ
S1-019P1์žฌ๊ณ  ์˜ˆ์•ฝ ๋กœ์ง๋ฐฑ์—”๋“œservices/stock.go๋™์‹œ์„ฑ ์ œ์–ด (SELECT FOR UPDATE)
S1-020P1์ฃผ๋ฌธ ์ทจ์†Œ API๋ฐฑ์—”๋“œhandlers/order.go๋ณด์ƒ ํŠธ๋žœ์žญ์…˜
S1-021P1Dockerfile ์ž‘์„ฑ (๋ฐฑ์—”๋“œ)์ธํ”„๋ผDockerfile๋ฉ€ํ‹ฐ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ
S1-022P1ECR ๋ ˆํฌ์ง€ํ† ๋ฆฌ ์ƒ์„ฑ์ธํ”„๋ผTerraform ์ฝ”๋“œ์ด๋ฏธ์ง€ ํ‘ธ์‹œ ์„ฑ๊ณต
S1-023P1EKS ํด๋Ÿฌ์Šคํ„ฐ ๊ตฌ์ถ•์ธํ”„๋ผTerraform ์ฝ”๋“œ๋…ธ๋“œ ๊ทธ๋ฃน 1๊ฐœ, t3.medium x 2

๐Ÿ“ S1-018: ์ฃผ๋ฌธ ์ƒ์„ฑ API (์‚ฌ๊ฐ€ ํŒจํ„ด)

// POST /api/v1/orders
// Request
{
    "time_deal_id": 1,
    "quantity": 2
}
 
// ๋‚ด๋ถ€ ํ”Œ๋กœ์šฐ (์‚ฌ๊ฐ€ ํŒจํ„ด)
// Step 1: ์ฃผ๋ฌธ ์ƒ์„ฑ (status: pending)
// Step 2: ์žฌ๊ณ  ์˜ˆ์•ฝ (reserved_quantity += quantity)
// Step 3: ์ฃผ๋ฌธ ์ƒํƒœ ๋ณ€๊ฒฝ (status: reserved)
// Step 4: (ํ–ฅํ›„) ๊ฒฐ์ œ ์ฒ˜๋ฆฌ
// Step 5: (ํ–ฅํ›„) ์ฃผ๋ฌธ ํ™•์ • (status: confirmed, sold_quantity += quantity, reserved_quantity -= quantity)
 
// Response 201
{
    "id": 123,
    "time_deal_id": 1,
    "quantity": 2,
    "unit_price": 29900,
    "total_price": 59800,
    "status": "reserved",
    "created_at": "2026-02-27T14:05:00Z"
}
 
// Error 409: ์žฌ๊ณ  ๋ถ€์กฑ
{
    "error": "INSUFFICIENT_STOCK",
    "message": "์žฌ๊ณ ๊ฐ€ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค",
    "available": 1,
    "requested": 2
}
 
// Error 400: ํƒ€์ž„๋”œ ๋น„ํ™œ์„ฑ
{
    "error": "DEAL_NOT_ACTIVE",
    "message": "ํƒ€์ž„๋”œ์ด ์ง„ํ–‰ ์ค‘์ด ์•„๋‹™๋‹ˆ๋‹ค"
}

๐Ÿ“ S1-019: ์žฌ๊ณ  ์˜ˆ์•ฝ ๋กœ์ง (๋™์‹œ์„ฑ ์ œ์–ด)

// services/stock.go
 
func (s *StockService) Reserve(ctx context.Context, dealID, quantity int) error {
    tx, _ := s.db.BeginTx(ctx, nil)
    defer tx.Rollback()
 
    // SELECT FOR UPDATE - ํ–‰ ์ž ๊ธˆ
    var deal TimeDeal
    err := tx.QueryRowContext(ctx, `
        SELECT id, stock_quantity, reserved_quantity, sold_quantity, status
        FROM time_deals
        WHERE id = $1
        FOR UPDATE
    `, dealID).Scan(&deal.ID, &deal.StockQty, &deal.ReservedQty, &deal.SoldQty, &deal.Status)
 
    if err != nil {
        return ErrDealNotFound
    }
 
    if deal.Status != "active" {
        return ErrDealNotActive
    }
 
    available := deal.StockQty - deal.ReservedQty - deal.SoldQty
    if available < quantity {
        return ErrInsufficientStock
    }
 
    // ์žฌ๊ณ  ์˜ˆ์•ฝ
    _, err = tx.ExecContext(ctx, `
        UPDATE time_deals
        SET reserved_quantity = reserved_quantity + $1,
            updated_at = CURRENT_TIMESTAMP
        WHERE id = $2
    `, quantity, dealID)
 
    return tx.Commit()
}

๐Ÿ“ S1-021: Dockerfile ๋ช…์„ธ

# backend/Dockerfile
 
# Build stage
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main .
 
# Runtime stage
FROM alpine:3.19
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]

๐Ÿ“ S1-023: EKS ๋ช…์„ธ

# terraform/eks.tf
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "19.0.0"
 
  cluster_name    = "bbossiregi-eks"
  cluster_version = "1.29"
 
  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets
 
  eks_managed_node_groups = {
    default = {
      name           = "default-ng"
      instance_types = ["t3.medium"]
      min_size       = 2
      max_size       = 4
      desired_size   = 2
    }
  }
}

Day 5 (2/28 ๊ธˆ) - ๋ฐฐํฌ ๋ฐ HTTPS

ID์šฐ์„ ์ˆœ์œ„ํƒœ์Šคํฌ๋‹ด๋‹น์‚ฐ์ถœ๋ฌผ์™„๋ฃŒ ๊ธฐ์ค€
S1-024P1K8s Deployment ๋งค๋‹ˆํŽ˜์ŠคํŠธ์ธํ”„๋ผk8s/deployment.yamlreplicas: 2
S1-025P1K8s Service ๋งค๋‹ˆํŽ˜์ŠคํŠธ์ธํ”„๋ผk8s/service.yamlClusterIP
S1-026P1ALB Ingress Controller์ธํ”„๋ผk8s/ingress.yamlALB ์ž๋™ ์ƒ์„ฑ
S1-027P1ACM ์ธ์ฆ์„œ ๋ฐœ๊ธ‰์ธํ”„๋ผTerraform ์ฝ”๋“œ*.bbossiregi.com
S1-028P2Route53 ๋„๋ฉ”์ธ ์—ฐ๊ฒฐ์ธํ”„๋ผTerraform ์ฝ”๋“œapi.bbossiregi.com
S1-029P1Lambda ํƒ€์ž„๋”œ ์Šค์ผ€์ค„๋Ÿฌ์ธํ”„๋ผlambda/scheduler.pyEventBridge ์—ฐ๋™
S1-030P0Sprint 1 ํšŒ๊ณ ์ „์ฒดKPT ๋ฌธ์„œํŒ€ ํšŒ๊ณ  ์™„๋ฃŒ

๐Ÿ“ S1-024: Deployment ๋ช…์„ธ

# k8s/backend/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  namespace: bbossiregi
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: backend
        image: 123456789.dkr.ecr.ap-northeast-2.amazonaws.com/bbossiregi-backend:v1
        ports:
        - containerPort: 8080
        env:
        - name: DB_HOST
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: host
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: password
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            cpu: "500m"
            memory: "512Mi"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5

๐Ÿ“ S1-029: Lambda ์Šค์ผ€์ค„๋Ÿฌ ๋ช…์„ธ

# lambda/scheduler.py
import boto3
import psycopg2
from datetime import datetime
 
def handler(event, context):
    """
    EventBridge์—์„œ ๋งค ๋ถ„๋งˆ๋‹ค ํ˜ธ์ถœ
    ํƒ€์ž„๋”œ ์ƒํƒœ ์ž๋™ ๋ณ€๊ฒฝ
    """
    conn = get_db_connection()
    cur = conn.cursor()
    now = datetime.utcnow()
    
    # scheduled โ†’ active
    cur.execute("""
        UPDATE time_deals 
        SET status = 'active', updated_at = %s
        WHERE status = 'scheduled' 
        AND start_at <= %s AND end_at > %s
    """, (now, now, now))
    activated = cur.rowcount
    
    # active โ†’ ended
    cur.execute("""
        UPDATE time_deals 
        SET status = 'ended', updated_at = %s
        WHERE status = 'active' AND end_at <= %s
    """, (now, now))
    ended = cur.rowcount
    
    # active โ†’ soldout
    cur.execute("""
        UPDATE time_deals 
        SET status = 'soldout', updated_at = %s
        WHERE status = 'active' 
        AND stock_quantity <= reserved_quantity + sold_quantity
    """, (now,))
    soldout = cur.rowcount
    
    conn.commit()
    
    return {
        'activated': activated,
        'ended': ended,
        'soldout': soldout
    }

๐Ÿ—“๏ธ Sprint 2: ํ”„๋ก ํŠธ์—”๋“œ + ํ…Œ์ŠคํŠธ (Day 6-10)

Day 6 (3/3 ์›”) - ํ”„๋ก ํŠธ์—”๋“œ ๊ธฐ๋ฐ˜

ID์šฐ์„ ์ˆœ์œ„ํƒœ์Šคํฌ๋‹ด๋‹น์‚ฐ์ถœ๋ฌผ์™„๋ฃŒ ๊ธฐ์ค€
S2-001P0React ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐํ™”ํ”„๋ก ํŠธpackage.jsonVite + React 19
S2-002P1๋ผ์šฐํ„ฐ ์„ค์ •ํ”„๋ก ํŠธrouter.jsx5๊ฐœ ํŽ˜์ด์ง€ ๋ผ์šฐํŠธ
S2-003P1API ํด๋ผ์ด์–ธํŠธ ์„ค์ •ํ”„๋ก ํŠธapi/client.jsAxios + ์ธํ„ฐ์…‰ํ„ฐ
S2-004P1๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€ํ”„๋ก ํŠธpages/Auth.jsxํผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํฌํ•จ
S2-005P1์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ ์„ค์ •ํ”„๋ก ํŠธstore/auth.jsZustand

๐Ÿ“ S2-002: ๋ผ์šฐํŠธ ๋ช…์„ธ

// src/router.jsx
const routes = [
  { path: '/', element: <Home /> },           // ๋ฉ”์ธ (ํƒ€์ž„๋”œ ๋ชฉ๋ก)
  { path: '/login', element: <Login /> },     // ๋กœ๊ทธ์ธ
  { path: '/register', element: <Register /> }, // ํšŒ์›๊ฐ€์ž…
  { path: '/deals/:id', element: <DealDetail /> }, // ํƒ€์ž„๋”œ ์ƒ์„ธ
  { path: '/orders', element: <OrderList /> },  // ๋‚ด ์ฃผ๋ฌธ ๋ชฉ๋ก
  { path: '/admin', element: <AdminDashboard /> }, // ๊ด€๋ฆฌ์ž (Protected)
];

Day 7 (3/4 ํ™”) - ํƒ€์ž„๋”œ UI

ID์šฐ์„ ์ˆœ์œ„ํƒœ์Šคํฌ๋‹ด๋‹น์‚ฐ์ถœ๋ฌผ์™„๋ฃŒ ๊ธฐ์ค€
S2-006P1ํƒ€์ž„๋”œ ๋ชฉ๋ก ํŽ˜์ด์ง€ํ”„๋ก ํŠธpages/Home.jsx์นด๋“œ ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ
S2-007P1์นด์šดํŠธ๋‹ค์šด ํƒ€์ด๋จธ ์ปดํฌ๋„ŒํŠธํ”„๋ก ํŠธcomponents/Countdown.jsx์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ
S2-008P1ํƒ€์ž„๋”œ ์ƒ์„ธ ํŽ˜์ด์ง€ํ”„๋ก ํŠธpages/DealDetail.jsx์žฌ๊ณ  ํ”„๋กœ๊ทธ๋ ˆ์Šค๋ฐ”
S2-009P1๊ตฌ๋งค ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธํ”„๋ก ํŠธcomponents/BuyButton.jsx์ƒํƒœ๋ณ„ UI ๋ถ„๊ธฐ

๐Ÿ“ S2-007: ์นด์šดํŠธ๋‹ค์šด ๋ช…์„ธ

// components/Countdown.jsx
// Props: targetTime (ISO string)
// ํ‘œ์‹œ ํ˜•์‹: "01:23:45" (์‹œ:๋ถ„:์ดˆ)
// ์ƒ‰์ƒ ๊ทœ์น™:
//   - 1์‹œ๊ฐ„ ์ด์ƒ: ๊ฒ€์ •
//   - 10๋ถ„ ์ดํ•˜: ์ฃผํ™ฉ
//   - 1๋ถ„ ์ดํ•˜: ๋นจ๊ฐ• + ๊นœ๋นก์ž„
// ์ข…๋ฃŒ ์‹œ: "์ข…๋ฃŒ๋จ" ํ‘œ์‹œ

๐Ÿ“ S2-009: ๊ตฌ๋งค ๋ฒ„ํŠผ ์ƒํƒœ

// ์ƒํƒœ๋ณ„ ๋ฒ„ํŠผ UI
{
  'scheduled': { text: '์˜คํ”ˆ ์˜ˆ์ •', disabled: true, color: 'gray' },
  'active': { text: '๊ตฌ๋งคํ•˜๊ธฐ', disabled: false, color: 'black' },
  'soldout': { text: 'ํ’ˆ์ ˆ', disabled: true, color: 'red' },
  'ended': { text: '์ข…๋ฃŒ๋จ', disabled: true, color: 'gray' },
}

Day 8 (3/5 ์ˆ˜) - ์ฃผ๋ฌธ ํ”Œ๋กœ์šฐ UI

ID์šฐ์„ ์ˆœ์œ„ํƒœ์Šคํฌ๋‹ด๋‹น์‚ฐ์ถœ๋ฌผ์™„๋ฃŒ ๊ธฐ์ค€
S2-010P1์ฃผ๋ฌธ ์ƒ์„ฑ ํ”Œ๋กœ์šฐํ”„๋ก ํŠธhooks/useOrder.js๋‚™๊ด€์  UI ์—…๋ฐ์ดํŠธ
S2-011P1์ฃผ๋ฌธ ๊ฒฐ๊ณผ ๋ชจ๋‹ฌํ”„๋ก ํŠธcomponents/OrderModal.jsx์„ฑ๊ณต/์‹คํŒจ ๋ถ„๊ธฐ
S2-012P1๋‚ด ์ฃผ๋ฌธ ๋ชฉ๋ก ํŽ˜์ด์ง€ํ”„๋ก ํŠธpages/OrderList.jsx์ƒํƒœ๋ณ„ ํ•„ํ„ฐ๋ง
S2-013P2ํ† ์ŠคํŠธ ์•Œ๋ฆผ ์ปดํฌ๋„ŒํŠธํ”„๋ก ํŠธcomponents/Toast.jsx์„ฑ๊ณต/์—๋Ÿฌ/์ •๋ณด

Day 9 (3/6 ๋ชฉ) - ํ…Œ์ŠคํŠธ ๊ธฐ๋ฐ˜

ID์šฐ์„ ์ˆœ์œ„ํƒœ์Šคํฌ๋‹ด๋‹น์‚ฐ์ถœ๋ฌผ์™„๋ฃŒ ๊ธฐ์ค€
S2-014P1๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์„ค์ • (๋ฐฑ์—”๋“œ)๋ฐฑ์—”๋“œ*_test.gogo test ์‹คํ–‰ ๊ฐ€๋Šฅ
S2-015P1์žฌ๊ณ  ์˜ˆ์•ฝ ํ…Œ์ŠคํŠธ๋ฐฑ์—”๋“œstock_test.go๋™์‹œ์„ฑ ํ…Œ์ŠคํŠธ ํฌํ•จ
S2-016P1API ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฐฑ์—”๋“œintegration_test.go์ฃผ์š” ํ”Œ๋กœ์šฐ ์ปค๋ฒ„
S2-017P2ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ ์„ค์ •ํ”„๋ก ํŠธvitest.config.jsVitest

๐Ÿ“ S2-015: ๋™์‹œ์„ฑ ํ…Œ์ŠคํŠธ ๋ช…์„ธ

// services/stock_test.go
 
func TestConcurrentReservation(t *testing.T) {
    // Given: ์žฌ๊ณ  10๊ฐœ์ธ ํƒ€์ž„๋”œ
    deal := createTestDeal(stock: 10)
    
    // When: ๋™์‹œ์— 15๋ช…์ด 1๊ฐœ์”ฉ ์˜ˆ์•ฝ ์‹œ๋„
    var wg sync.WaitGroup
    successCount := atomic.Int32{}
    failCount := atomic.Int32{}
    
    for i := 0; i < 15; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            err := stockService.Reserve(ctx, deal.ID, 1)
            if err == nil {
                successCount.Add(1)
            } else {
                failCount.Add(1)
            }
        }()
    }
    wg.Wait()
    
    // Then: ์ •ํ™•ํžˆ 10๋ช…๋งŒ ์„ฑ๊ณต
    assert.Equal(t, int32(10), successCount.Load())
    assert.Equal(t, int32(5), failCount.Load())
    
    // And: ์žฌ๊ณ  ์ •ํ•ฉ์„ฑ ์œ ์ง€
    deal = getDeal(deal.ID)
    assert.Equal(t, 10, deal.ReservedQuantity)
}

Day 10 (3/7 ๊ธˆ) - CI/CD ๊ตฌ์ถ•

ID์šฐ์„ ์ˆœ์œ„ํƒœ์Šคํฌ๋‹ด๋‹น์‚ฐ์ถœ๋ฌผ์™„๋ฃŒ ๊ธฐ์ค€
S2-018P1GitHub Actions ์›Œํฌํ”Œ๋กœ์šฐ์ธํ”„๋ผ.github/workflows/ci.ymlํ…Œ์ŠคํŠธ + ๋นŒ๋“œ
S2-019P1ECR ํ‘ธ์‹œ ์ž๋™ํ™”์ธํ”„๋ผ.github/workflows/cd.ymlmain ๋ธŒ๋žœ์น˜ ๋จธ์ง€ ์‹œ
S2-020P1K8s ๋ฐฐํฌ ์ž๋™ํ™”์ธํ”„๋ผ.github/workflows/cd.ymlkubectl apply
S2-021P2Slack ์•Œ๋ฆผ ์—ฐ๋™์ธํ”„๋ผ.github/workflows/*.yml์„ฑ๊ณต/์‹คํŒจ ์•Œ๋ฆผ
S2-022P0Sprint 2 ํšŒ๊ณ ์ „์ฒดKPT ๋ฌธ์„œํŒ€ ํšŒ๊ณ  ์™„๋ฃŒ

๐Ÿ“ S2-018: CI ์›Œํฌํ”Œ๋กœ์šฐ ๋ช…์„ธ

# .github/workflows/ci.yml
name: CI
 
on:
  pull_request:
    branches: [main]
 
jobs:
  test-backend:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.22'
      - run: cd backend && go test -v ./...
      
  test-frontend:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: cd frontend && npm ci && npm test
      
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: golangci/golangci-lint-action@v4
        with:
          working-directory: backend

๐Ÿ—“๏ธ Sprint 3: ๋ชจ๋‹ˆํ„ฐ๋ง + ๋ถ€ํ•˜ํ…Œ์ŠคํŠธ (Day 11-15)

Day 11-12: ๋ชจ๋‹ˆํ„ฐ๋ง ์Šคํƒ

ID์šฐ์„ ์ˆœ์œ„ํƒœ์Šคํฌ๋‹ด๋‹น์‚ฐ์ถœ๋ฌผ์™„๋ฃŒ ๊ธฐ์ค€
S3-001P1Prometheus ์„ค์น˜์ธํ”„๋ผhelm chart๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘ ํ™•์ธ
S3-002P1Grafana ์„ค์น˜์ธํ”„๋ผhelm chart๋Œ€์‹œ๋ณด๋“œ ์ ‘์† ๊ฐ€๋Šฅ
S3-003P1๋ฐฑ์—”๋“œ ๋ฉ”ํŠธ๋ฆญ ๋…ธ์ถœ๋ฐฑ์—”๋“œ/metrics ์—”๋“œํฌ์ธํŠธPrometheus ํฌ๋งท
S3-004P1Grafana ๋Œ€์‹œ๋ณด๋“œ ๊ตฌ์„ฑ์ธํ”„๋ผdashboard.jsonRPS, P99, ์—๋Ÿฌ์œจ
S3-005P2CloudWatch ๋กœ๊ทธ ์—ฐ๋™์ธํ”„๋ผFluent Bit๋กœ๊ทธ ์ค‘์•™ํ™”

๐Ÿ“ S3-003: ๋ฐฑ์—”๋“œ ๋ฉ”ํŠธ๋ฆญ ๋ช…์„ธ

// ๋…ธ์ถœํ•  ๋ฉ”ํŠธ๋ฆญ
http_requests_total{method, path, status}     // ์š”์ฒญ ์ˆ˜
http_request_duration_seconds{method, path}   // ์‘๋‹ต ์‹œ๊ฐ„
stock_reservation_total{deal_id, result}      // ์žฌ๊ณ  ์˜ˆ์•ฝ ์ˆ˜
active_deals_count                            // ํ™œ์„ฑ ํƒ€์ž„๋”œ ์ˆ˜

Day 13-14: ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ

ID์šฐ์„ ์ˆœ์œ„ํƒœ์Šคํฌ๋‹ด๋‹น์‚ฐ์ถœ๋ฌผ์™„๋ฃŒ ๊ธฐ์ค€
S3-006P1k6 ํ…Œ์ŠคํŠธ ์Šคํฌ๋ฆฝํŠธ ์ž‘์„ฑ์ „์ฒดk6/load-test.js์‹œ๋‚˜๋ฆฌ์˜ค 3๊ฐœ
S3-007P1๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ์‹คํ–‰์ „์ฒด๊ฒฐ๊ณผ ๋ฆฌํฌํŠธ1000 VU ํ…Œ์ŠคํŠธ
S3-008P1๋ณ‘๋ชฉ ์ง€์  ๋ถ„์„์ „์ฒด๋ถ„์„ ๋ฌธ์„œ๊ฐœ์„  ํฌ์ธํŠธ ๋„์ถœ
S3-009P1์„ฑ๋Šฅ ์ตœ์ ํ™” ์ ์šฉ๋ฐฑ์—”๋“œ์ฝ”๋“œ ์ˆ˜์ •P99 < 500ms

๐Ÿ“ S3-006: k6 ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค

// k6/load-test.js
 
// ์‹œ๋‚˜๋ฆฌ์˜ค 1: ํƒ€์ž„๋”œ ์กฐํšŒ ๋ถ€ํ•˜
export function browsing() {
  http.get(`${BASE_URL}/api/v1/timedeals`);
  sleep(1);
}
 
// ์‹œ๋‚˜๋ฆฌ์˜ค 2: ๋™์‹œ ๊ตฌ๋งค ๋ถ€ํ•˜ (ํƒ€์ž„๋”œ ์˜คํ”ˆ ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
export function purchase() {
  const res = http.post(`${BASE_URL}/api/v1/orders`, JSON.stringify({
    time_deal_id: 1,
    quantity: 1
  }), { headers: { 'Content-Type': 'application/json' } });
  
  check(res, {
    'status is 201 or 409': (r) => r.status === 201 || r.status === 409,
  });
}
 
// ์‹œ๋‚˜๋ฆฌ์˜ค 3: ํ˜ผํ•ฉ ๋ถ€ํ•˜
export const options = {
  scenarios: {
    browsing: {
      executor: 'constant-vus',
      vus: 100,
      duration: '5m',
      exec: 'browsing',
    },
    purchase: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '30s', target: 500 },   // 30์ดˆ๊ฐ„ 500๋ช…๊นŒ์ง€ ์ฆ๊ฐ€
        { duration: '1m', target: 1000 },   // 1๋ถ„๊ฐ„ 1000๋ช…๊นŒ์ง€ ์ฆ๊ฐ€
        { duration: '30s', target: 0 },     // 30์ดˆ๊ฐ„ ๊ฐ์†Œ
      ],
      exec: 'purchase',
    },
  },
};

Day 15: HPA + ์Šค์ผ€์ผ๋ง ํ…Œ์ŠคํŠธ

ID์šฐ์„ ์ˆœ์œ„ํƒœ์Šคํฌ๋‹ด๋‹น์‚ฐ์ถœ๋ฌผ์™„๋ฃŒ ๊ธฐ์ค€
S3-010P1HPA ์„ค์ •์ธํ”„๋ผk8s/hpa.yamlCPU 70% ๊ธฐ์ค€
S3-011P1์Šค์ผ€์ผ๋ง ํ…Œ์ŠคํŠธ์ธํ”„๋ผํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ30์ดˆ ๋‚ด ์Šค์ผ€์ผ ์•„์›ƒ
S3-012P2PDB ์„ค์ •์ธํ”„๋ผk8s/pdb.yamlminAvailable: 1
S3-013P0Sprint 3 ํšŒ๊ณ  + ์ค‘๊ฐ„ ๋ฐœํ‘œ ์ค€๋น„์ „์ฒด๋ฐœํ‘œ ์ž๋ฃŒ3/9 ์ค‘๊ฐ„ ๋ฐœํ‘œ

๐Ÿ“ S3-010: HPA ๋ช…์„ธ

# k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: backend-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: backend
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
      - type: Pods
        value: 4
        periodSeconds: 60

๐Ÿ—“๏ธ Sprint 4: ๋งˆ๋ฌด๋ฆฌ + ๋ฐœํ‘œ (Day 16-20)

Day 16-17: ๋ฌธ์„œํ™”

ID์šฐ์„ ์ˆœ์œ„ํƒœ์Šคํฌ๋‹ด๋‹น์‚ฐ์ถœ๋ฌผ์™„๋ฃŒ ๊ธฐ์ค€
S4-001P0์•„ํ‚คํ…์ฒ˜ ๋ฌธ์„œ ์ตœ์ข…ํ™”์ „์ฒดADR ๋ฌธ์„œ์˜์‚ฌ๊ฒฐ์ • ๊ธฐ๋ก
S4-002P0API ๋ฌธ์„œ ์™„์„ฑ๋ฐฑ์—”๋“œSwagger UI์ „์ฒด API ๋ฌธ์„œํ™”
S4-003P0์šด์˜ ๊ฐ€์ด๋“œ ์ž‘์„ฑ์ธํ”„๋ผRUNBOOK.md์žฅ์•  ๋Œ€์‘ ์ ˆ์ฐจ
S4-004P1README ์™„์„ฑ์ „์ฒดREADME.md์„ค์น˜/์‹คํ–‰ ๊ฐ€์ด๋“œ

Day 18-19: ๋ฐœํ‘œ ์ค€๋น„

ID์šฐ์„ ์ˆœ์œ„ํƒœ์Šคํฌ๋‹ด๋‹น์‚ฐ์ถœ๋ฌผ์™„๋ฃŒ ๊ธฐ์ค€
S4-005P0๊ฒฐ๊ณผ ๋ณด๊ณ ์„œ ์ž‘์„ฑ์ „์ฒด๋ณด๊ณ ์„œ.pdfํ…œํ”Œ๋ฆฟ ์ค€์ˆ˜
S4-006P0๋ฐœํ‘œ PPT ์ž‘์„ฑ์ „์ฒด๋ฐœํ‘œ.pptx15๋ถ„ ๋ถ„๋Ÿ‰
S4-007P0์‹œ์—ฐ ์˜์ƒ ์ œ์ž‘์ „์ฒดdemo.mp43-5๋ถ„
S4-008P1๋ฐœํ‘œ ๋ฆฌํ—ˆ์„ค์ „์ฒด-2ํšŒ ์ด์ƒ

Day 20 (3/19): ์ตœ์ข… ๋ฐœํ‘œ

ID์šฐ์„ ์ˆœ์œ„ํƒœ์Šคํฌ๋‹ด๋‹น์‚ฐ์ถœ๋ฌผ์™„๋ฃŒ ๊ธฐ์ค€
S4-009P0์ตœ์ข… ๋ฐœํ‘œ์ „์ฒด-15๋ถ„ ๋ฐœํ‘œ ์™„๋ฃŒ
S4-010P0๊ฒฐ๊ณผ๋ฌผ ์ œ์ถœํŒ€์žฅ๊ตฌ๊ธ€ํผ13:00๊นŒ์ง€
S4-011P0ํ”„๋กœ์ ํŠธ ํšŒ๊ณ ์ „์ฒดKPT ๋ฌธ์„œ์ตœ์ข… ํšŒ๊ณ 

๐Ÿ“Š Definition of Done (DoD)

์ฝ”๋“œ

  • PR ์ƒ์„ฑ ๋ฐ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์™„๋ฃŒ
  • ํ…Œ์ŠคํŠธ ํ†ต๊ณผ (์ปค๋ฒ„๋ฆฌ์ง€ 70% ์ด์ƒ)
  • lint ์—๋Ÿฌ ์—†์Œ
  • main ๋ธŒ๋žœ์น˜ ๋จธ์ง€ ์™„๋ฃŒ

์ธํ”„๋ผ

  • Terraform plan ์„ฑ๊ณต
  • ๋ฆฌ์†Œ์Šค ์ƒ์„ฑ ์™„๋ฃŒ
  • ํ—ฌ์Šค์ฒดํฌ ํ†ต๊ณผ
  • ๋ชจ๋‹ˆํ„ฐ๋ง ๋Œ€์‹œ๋ณด๋“œ์— ํ‘œ์‹œ

๋ฌธ์„œ

  • ๋…ธ์…˜ ํŽ˜์ด์ง€ ์—…๋ฐ์ดํŠธ
  • ADR ์ž‘์„ฑ (์˜์‚ฌ๊ฒฐ์ • ์‚ฌํ•ญ)
  • README ๋ฐ˜์˜