Skip to content

Latest commit

ย 

History

History
327 lines (260 loc) ยท 8.18 KB

File metadata and controls

327 lines (260 loc) ยท 8.18 KB

๐Ÿ’Œ Loveventure Course Service

๐Ÿ“Œ ์„œ๋น„์Šค ๊ฐœ์š”

PitterPetter Course Service๋Š” ์ปคํ”Œ์„ ์œ„ํ•œ ๋ฐ์ดํŠธ ์ฝ”์Šค ์ถ”์ฒœ ๋ฐ ๊ด€๋ฆฌ ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค. ๋„๋ฉ”์ธ ์ฃผ๋„ ์„ค๊ณ„(DDD)๋ฅผ ์ ์šฉํ•˜์—ฌ ์•ˆ์ •์ ์ด๊ณ  ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.


๐Ÿš€ ์ฃผ์š” ๊ธฐ๋Šฅ

๐ŸŽฏ ์ฝ”์Šค ๊ด€๋ฆฌ

  • ์ฝ”์Šค ์ƒ์„ฑ/์กฐํšŒ/์‚ญ์ œ: ์ปคํ”Œ๋ณ„ ๊ฐœ์ธํ™”๋œ ๋ฐ์ดํŠธ ์ฝ”์Šค ๊ด€๋ฆฌ
  • ์ฝ”์Šค ํ‰์  ์‹œ์Šคํ…œ: 0-10์  ๋ฆฌ๋ทฐ ์‹œ์Šคํ…œ
  • ํŠธ๋žœ์žญ์…˜ ๋ณด์žฅ: ๋‹จ์ผ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ๋ณด์žฅ

๐Ÿ—บ POI (Point of Interest) ๊ด€๋ฆฌ

  • ์ค‘์•™ํ™”๋œ ์žฅ์†Œ ์ •๋ณด: ์ค‘๋ณต ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ UPSERT ํŒจํ„ด
  • ๋ฐ์ดํ„ฐ ์ •๊ทœํ™”: ์˜์—…์‹œ๊ฐ„, ํƒœ๊ทธ, ๋งํฌ ๋“ฑ ์ฒด๊ณ„์  ๊ด€๋ฆฌ
  • JSONB ํ™œ์šฉ: ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์œ ์—ฐํ•˜๊ฒŒ ์ฒ˜๋ฆฌ

๐Ÿ” ๋ณด์•ˆ ๋ฐ ์ธ์ฆ

  • JWT ๊ธฐ๋ฐ˜ ์ธ์ฆ: OAuth2 Resource Server
  • ๋ฐ์ดํ„ฐ ๊ฒฉ๋ฆฌ: couple_id ๊ธฐ๋ฐ˜ ์™„์ „ ๊ฒฉ๋ฆฌ
  • ๊ถŒํ•œ ๊ด€๋ฆฌ: ์ปคํ”Œ๋ณ„ ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ์ œ์–ด

๐Ÿ›  ๊ธฐ์ˆ  ์Šคํƒ

Backend

  • Java 17 + Spring Boot 3.4.10
  • Spring Security + OAuth2 Resource Server
  • Spring Data JPA + PostgreSQL
  • SpringDoc OpenAPI 3 (Swagger)

Build & Deploy

  • Gradle ๋นŒ๋“œ ๋„๊ตฌ
  • Docker ์ปจํ…Œ์ด๋„ˆํ™”
  • Spring Cloud Config ์ค‘์•™ ์„ค์ • ๊ด€๋ฆฌ
  • API Gateway ์—ฐ๋™
  • Kubernetes ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜

์ถ”๊ฐ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • JWT (jjwt): JWT ํ† ํฐ ์ฒ˜๋ฆฌ
  • Lombok: ์ฝ”๋“œ ๊ฐ„์†Œํ™”
  • Jackson: JSON ์ฒ˜๋ฆฌ
  • Spring Cloud Config: ์ค‘์•™ ์„ค์ • ๊ด€๋ฆฌ
  • Spring Cloud Gateway: API Gateway ์—ฐ๋™

๐Ÿ—๏ธ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„

๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜

API Gateway โ†’ Course Service (8081)
     โ†“              โ†“
Config Server    Database
     โ†“              โ†“
Auth Service    PostgreSQL

๋„๋ฉ”์ธ ๋ชจ๋ธ

Course (1) โ†โ†’ (N) PoiSet (N) โ†โ†’ (1) POI
  โ†“
Couple (์™ธ๋ถ€ ์‹œ์Šคํ…œ)

ํ•ต์‹ฌ ์—”ํ‹ฐํ‹ฐ

  • Course: ์ปคํ”Œ๋ณ„ ์ฝ”์Šค ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ (UUID ๊ธฐ๋ฐ˜)
  • POI: ์ค‘์•™ํ™”๋œ ์žฅ์†Œ ์ •๋ณด (์œ„์น˜ ๊ธฐ๋ฐ˜ UNIQUE)
  • PoiSet: ์ฝ”์Šค-POI ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ” (์ˆœ์„œ ํฌํ•จ)
  • Category: ์žฅ์†Œ ์นดํ…Œ๊ณ ๋ฆฌ enum

์„ฑ๋Šฅ ์ตœ์ ํ™”

  • ์ธ๋ฑ์Šค ์ „๋žต: couple_id, category, mood_tag, location ์ธ๋ฑ์Šค
  • JOIN FETCH: N+1 ์ฟผ๋ฆฌ ํ•ด๊ฒฐ
  • LAZY ๋กœ๋”ฉ: ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์„ฑ
  • ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง: ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ ๋ฐ ์ค‘๋ณต๋ฅ  ์ถ”์ 

โš™๏ธ ํ™˜๊ฒฝ ์„ค์ •

Config Server ์—ฐ๋™

์ด ์„œ๋น„์Šค๋Š” Spring Cloud Config Server๋ฅผ ํ†ตํ•ด ์ค‘์•™ํ™”๋œ ์„ค์ • ๊ด€๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

# application.yaml
spring:
  config:
    import: "optional:configserver:"
  cloud:
    config:
      uri: http://config-server.config-server.svc.cluster.local:80
      name: course-service
      label: main
      fail-fast: true
  application:
    name: course-service
  profiles:
    active: prod

API Gateway ์—ฐ๋™

API Gateway๋ฅผ ํ†ตํ•ด ์™ธ๋ถ€ ์š”์ฒญ์„ ๋ผ์šฐํŒ…ํ•˜๋ฉฐ, ๋‚ด๋ถ€ ์„œ๋น„์Šค ํฌํŠธ๋Š” 8081์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

# ๋‚ด๋ถ€ ์„œ๋น„์Šค ํฌํŠธ
SERVER_PORT=8081

# API Gateway๋ฅผ ํ†ตํ•œ ์™ธ๋ถ€ ์ ‘๊ทผ
# Gateway URL: http://api-gateway:8080
# Service Path: /course-service

โ–ถ ์‹คํ–‰ ๋ฐฉ๋ฒ•

๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ

# ์˜์กด์„ฑ ์„ค์น˜
./gradlew build

# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ (Config Server ์—ฐ๋™)
./gradlew bootRun

Docker ์‹คํ–‰ (๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ํ™˜๊ฒฝ)

# Docker ์ด๋ฏธ์ง€ ๋นŒ๋“œ
docker build -t pitterpetter-course-service .

# ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰ (Config Server ์—ฐ๋™)
docker run -p 8081:8081 \
  --network microservices-network \
  pitterpetter-course-service

Kubernetes ๋ฐฐํฌ

# Config Server์™€ ํ•จ๊ป˜ ๋ฐฐํฌ
kubectl apply -f k8s/course-service-deployment.yaml
kubectl apply -f k8s/course-service-service.yaml

๐Ÿ“ก API ์—”๋“œํฌ์ธํŠธ

๋‚ด๋ถ€ ์„œ๋น„์Šค ์—”๋“œํฌ์ธํŠธ (ํฌํŠธ 8081)

Method Endpoint ์„ค๋ช… ์ธ์ฆ
POST /api/courses ์ฝ”์Šค ์ƒ์„ฑ โœ…
GET /api/courses ์ปคํ”Œ๋ณ„ ์ฝ”์Šค ๋ชฉ๋ก ์กฐํšŒ โœ…
DELETE /api/courses/{courseId} ์ฝ”์Šค ์‚ญ์ œ โœ…
PATCH /api/courses/{courseId}/review ์ฝ”์Šค ํ‰์  ์—…๋ฐ์ดํŠธ โœ…

API Gateway๋ฅผ ํ†ตํ•œ ์™ธ๋ถ€ ์ ‘๊ทผ

Method Gateway Endpoint ์„ค๋ช… ์ธ์ฆ
POST /api/courses ์ฝ”์Šค ์ƒ์„ฑ โœ…
GET /api/courses ์ปคํ”Œ๋ณ„ ์ฝ”์Šค ๋ชฉ๋ก ์กฐํšŒ โœ…
DELETE /api/courses/{courseId} ์ฝ”์Šค ์‚ญ์ œ โœ…
PATCH /api/course/{courseId}/review ์ฝ”์Šค ํ‰์  ์—…๋ฐ์ดํŠธ โœ…

API ๋ฌธ์„œ

  • ๋‚ด๋ถ€ Swagger UI: http://localhost:8081/swagger-ui.html
  • Gateway๋ฅผ ํ†ตํ•œ ์ ‘๊ทผ: http://api-gateway:8080/course-service/swagger-ui.html
  • OpenAPI JSON: http://localhost:8081/v3/api-docs

๐Ÿ—„๏ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค๊ณ„

์ฃผ์š” ํ…Œ์ด๋ธ”

  • course: ์ฝ”์Šค ์ •๋ณด (UUID ๊ธฐ๋ฐ˜ ID)
  • poi: ์žฅ์†Œ ์ •๋ณด (์œ„์น˜ ๊ธฐ๋ฐ˜ UNIQUE)
  • poi_set: ์ฝ”์Šค-POI ์—ฐ๊ฒฐ (์ˆœ์„œ ํฌํ•จ)
  • poi_food_tags: POI ์Œ์‹ ํƒœ๊ทธ (ElementCollection)

์ธ๋ฑ์Šค ์ „๋žต

  • course: couple_id, couple_id + created_at
  • poi_set: course_id + order, poi_id
  • poi: category, mood_tag, lat + lng

๐Ÿ”ง ๊ฐœ๋ฐœ ๊ฐ€์ด๋“œ

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

src/main/java/com/example/course/
โ”œโ”€โ”€ api/                    # API ๋ ˆ์ด์–ด
โ”‚   โ”œโ”€โ”€ controller/        # REST ์ปจํŠธ๋กค๋Ÿฌ
โ”‚   โ””โ”€โ”€ dto/              # ์š”์ฒญ/์‘๋‹ต DTO
โ”œโ”€โ”€ config/               # ์„ค์ • ํด๋ž˜์Šค
โ”œโ”€โ”€ domain/               # ๋„๋ฉ”์ธ ๋ชจ๋ธ
โ”‚   โ”œโ”€โ”€ service/          # ๋„๋ฉ”์ธ ์„œ๋น„์Šค
โ”‚   โ””โ”€โ”€ Category.java     # ์นดํ…Œ๊ณ ๋ฆฌ enum
โ”œโ”€โ”€ exception/            # ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
โ”œโ”€โ”€ jwt/                  # JWT ์ฒ˜๋ฆฌ
โ”œโ”€โ”€ repository/           # ๋ฐ์ดํ„ฐ ์ ‘๊ทผ
โ””โ”€โ”€ service/              # ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง

์ฝ”๋“œ ์Šคํƒ€์ผ

  • ๋„๋ฉ”์ธ ์ฃผ๋„ ์„ค๊ณ„: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋„๋ฉ”์ธ์— ์บก์Аํ™”
  • ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜: ๊ณ„์ธต ๋ถ„๋ฆฌ ๋ฐ ์˜์กด์„ฑ ์—ญ์ „
  • ๋‹จ์ผ ์ฑ…์ž„ ์›์น™: ๊ฐ ํด๋ž˜์Šค์˜ ๋ช…ํ™•ํ•œ ์—ญํ•  ๋ถ„๋‹ด

๐Ÿงช ํ…Œ์ŠคํŠธ

๋‹จ์œ„ ํ…Œ์ŠคํŠธ

# ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์‹คํ–‰
./gradlew test

# ํŠน์ • ํ…Œ์ŠคํŠธ ์‹คํ–‰
./gradlew test --tests "CourseServiceTest"

ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ

# ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์‹คํ–‰
./gradlew integrationTest

๐Ÿ“Š ๋ชจ๋‹ˆํ„ฐ๋ง

์„ฑ๋Šฅ ์ง€ํ‘œ

  • ์‘๋‹ต ์‹œ๊ฐ„: p95 150-200ms ๋ชฉํ‘œ
  • ์ฒ˜๋ฆฌ๋Ÿ‰: 1000 req/s ๋ชฉํ‘œ
  • ์ค‘๋ณต๋ฅ : POI ์ค‘๋ณต๋ฅ  1% ๋ฏธ๋งŒ

๋กœ๊ทธ ๋ชจ๋‹ˆํ„ฐ๋ง

  • ์ฝ”์Šค ์ƒ์„ฑ ์‹œ๊ฐ„: ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ ์ธก์ •
  • POI ์ค‘๋ณต ๊ฐ์ง€: ์ค‘๋ณต๋ฅ  ์ถ”์ 
  • ์—๋Ÿฌ ๋กœ๊ทธ: ์ƒ์„ธํ•œ ์—๋Ÿฌ ์ •๋ณด

๐Ÿš€ ๋ฐฐํฌ

๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ํ™˜๊ฒฝ ๋ฐฐํฌ

# Docker ์ด๋ฏธ์ง€ ๋นŒ๋“œ
docker build -t pitterpetter-course-service:latest .

# ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๋„คํŠธ์›Œํฌ์—์„œ ์‹คํ–‰
docker run -d \
  --name course-service \
  --network microservices-network \
  -p 8081:8081 \
  pitterpetter-course-service:latest

Kubernetes ๋ฐฐํฌ (Config Server ์—ฐ๋™)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: course-service
  namespace: microservices
spec:
  replicas: 3
  selector:
    matchLabels:
      app: course-service
  template:
    metadata:
      labels:
        app: course-service
    spec:
      containers:
      - name: course-service
        image: pitterpetter-course-service:latest
        ports:
        - containerPort: 8081
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "prod"
        - name: SERVER_PORT
          value: "8081"
---
apiVersion: v1
kind: Service
metadata:
  name: course-service
  namespace: microservices
spec:
  selector:
    app: course-service
  ports:
  - port: 8081
    targetPort: 8081
  type: ClusterIP

๐Ÿงฉ ๋ธŒ๋žœ์น˜ ์ „๋žต

  • main: ๋ฐฐํฌ์šฉ ์•ˆ์ • ๋ฒ„์ „
  • develop: ํ†ตํ•ฉ ๊ฐœ๋ฐœ ๋ธŒ๋žœ์น˜
  • feature/PIT-์ด์Šˆ๋ฒˆํ˜ธ: ๊ธฐ๋Šฅ ๋‹จ์œ„ ๊ฐœ๋ฐœ ๋ธŒ๋žœ์น˜
  • hotfix/PIT-์ด์Šˆ๋ฒˆํ˜ธ: ๊ธด๊ธ‰ ์ˆ˜์ • ๋ธŒ๋žœ์น˜

๐Ÿ“œ ์ปค๋ฐ‹ ๊ทœ์น™

  • feat: ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€
  • fix: ๋ฒ„๊ทธ ์ˆ˜์ •
  • docs: ๋ฌธ์„œ ์ˆ˜์ •
  • refactor: ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง
  • test: ํ…Œ์ŠคํŠธ ์ฝ”๋“œ
  • perf: ์„ฑ๋Šฅ ๊ฐœ์„ 
  • chore: ๋นŒ๋“œ ๊ณผ์ • ๋˜๋Š” ๋ณด์กฐ ๋„๊ตฌ ๋ณ€๊ฒฝ