PitterPetter Course Service๋ ์ปคํ์ ์ํ ๋ฐ์ดํธ ์ฝ์ค ์ถ์ฒ ๋ฐ ๊ด๋ฆฌ ๋ฐฑ์๋ ์๋น์ค์ ๋๋ค. ๋๋ฉ์ธ ์ฃผ๋ ์ค๊ณ(DDD)๋ฅผ ์ ์ฉํ์ฌ ์์ ์ ์ด๊ณ ํ์ฅ ๊ฐ๋ฅํ ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ๋ฅผ ๊ตฌํํ์ต๋๋ค.
- ์ฝ์ค ์์ฑ/์กฐํ/์ญ์ : ์ปคํ๋ณ ๊ฐ์ธํ๋ ๋ฐ์ดํธ ์ฝ์ค ๊ด๋ฆฌ
- ์ฝ์ค ํ์ ์์คํ : 0-10์ ๋ฆฌ๋ทฐ ์์คํ
- ํธ๋์ญ์ ๋ณด์ฅ: ๋จ์ผ ํธ๋์ญ์ ์ผ๋ก ๋ฐ์ดํฐ ์ผ๊ด์ฑ ๋ณด์ฅ
- ์ค์ํ๋ ์ฅ์ ์ ๋ณด: ์ค๋ณต ๋ฐฉ์ง๋ฅผ ์ํ UPSERT ํจํด
- ๋ฐ์ดํฐ ์ ๊ทํ: ์์ ์๊ฐ, ํ๊ทธ, ๋งํฌ ๋ฑ ์ฒด๊ณ์ ๊ด๋ฆฌ
- JSONB ํ์ฉ: ๋ณต์กํ ๋ฐ์ดํฐ ๊ตฌ์กฐ ์ ์ฐํ๊ฒ ์ฒ๋ฆฌ
- JWT ๊ธฐ๋ฐ ์ธ์ฆ: OAuth2 Resource Server
- ๋ฐ์ดํฐ ๊ฒฉ๋ฆฌ: couple_id ๊ธฐ๋ฐ ์์ ๊ฒฉ๋ฆฌ
- ๊ถํ ๊ด๋ฆฌ: ์ปคํ๋ณ ๋ฐ์ดํฐ ์ ๊ทผ ์ ์ด
- Java 17 + Spring Boot 3.4.10
- Spring Security + OAuth2 Resource Server
- Spring Data JPA + PostgreSQL
- SpringDoc OpenAPI 3 (Swagger)
- 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 ๋ก๋ฉ: ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ
- ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง: ์ฒ๋ฆฌ ์๊ฐ ๋ฐ ์ค๋ณต๋ฅ ์ถ์
์ด ์๋น์ค๋ 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: prodAPI 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 build -t pitterpetter-course-service .
# ์ปจํ
์ด๋ ์คํ (Config Server ์ฐ๋)
docker run -p 8081:8081 \
--network microservices-network \
pitterpetter-course-service# Config Server์ ํจ๊ป ๋ฐฐํฌ
kubectl apply -f k8s/course-service-deployment.yaml
kubectl apply -f k8s/course-service-service.yaml| Method | Endpoint | ์ค๋ช | ์ธ์ฆ |
|---|---|---|---|
| POST | /api/courses |
์ฝ์ค ์์ฑ | โ |
| GET | /api/courses |
์ปคํ๋ณ ์ฝ์ค ๋ชฉ๋ก ์กฐํ | โ |
| DELETE | /api/courses/{courseId} |
์ฝ์ค ์ญ์ | โ |
| PATCH | /api/courses/{courseId}/review |
์ฝ์ค ํ์ ์ ๋ฐ์ดํธ | โ |
| Method | Gateway Endpoint | ์ค๋ช | ์ธ์ฆ |
|---|---|---|---|
| POST | /api/courses |
์ฝ์ค ์์ฑ | โ |
| GET | /api/courses |
์ปคํ๋ณ ์ฝ์ค ๋ชฉ๋ก ์กฐํ | โ |
| DELETE | /api/courses/{courseId} |
์ฝ์ค ์ญ์ | โ |
| PATCH | /api/course/{courseId}/review |
์ฝ์ค ํ์ ์ ๋ฐ์ดํธ | โ |
- ๋ด๋ถ 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:latestapiVersion: 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: ๋น๋ ๊ณผ์ ๋๋ ๋ณด์กฐ ๋๊ตฌ ๋ณ๊ฒฝ