AI ๊ธฐ๋ฐ ๊ฒ์ ํ๋ ์ดํ ์คํธ ํ๋ซํผ์ Backend API Server
PlayProbie๋ ๊ฒ์ ๊ฐ๋ฐ์ฌ๊ฐ ํ๋ ์ดํ ์คํธ๋ฅผ ์ค๊ณํ๊ณ , ํ ์คํฐ๊ฐ ๋ธ๋ผ์ฐ์ ์์ ๊ฒ์์ ํ๋ ์ดํ ๋ค AI ์ธํฐ๋ทฐ๋ฅผ ํตํด ์ฌ์ธต ํผ๋๋ฐฑ์ ์์งํ๋ ์ฌ์ธ์ ํ๋ ์ดํ ์คํธ ์๋ฃจ์ ์ ๋๋ค.
์ด ์๋ฒ๋ React ํด๋ผ์ด์ธํธ์ FastAPI AI ์๋ฒ ์ฌ์ด์์ ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง์ ๋ด๋นํ๋ฉฐ, ๋ค์ ์ญํ ์ ์ํํฉ๋๋ค:
| ์ญํ | ์ค๋ช |
|---|---|
| ๐ ์ธ์ฆ/์ธ๊ฐ | JWT ๊ธฐ๋ฐ ์ฌ์ฉ์ ์ธ์ฆ ๋ฐ ์ํฌ์คํ์ด์ค ๊ถํ ๊ด๋ฆฌ |
| ๐ ์ค๋ฌธ ๊ด๋ฆฌ | ํ ๋ง ๊ธฐ๋ฐ ์ค๋ฌธ ์ค๊ณ, ์ํ ์ ์ด(DRAFTโACTIVEโCLOSED) |
| ๐ฎ ์คํธ๋ฆฌ๋ฐ ์ค์ผ์คํธ๋ ์ด์ | AWS GameLift Streams ๋ฆฌ์์ค ํ๋ก๋น์ ๋ ๋ฐ JIT ํ ๋น |
| ๐ค AI ์๋ฒ ํตํฉ | FastAPI AI ์๋ฒ์์ ๋น๋๊ธฐ ํต์ (์ง๋ฌธ ์์ฑ, ๋ถ์) |
| ๐ก ์ค์๊ฐ ํต์ | SSE ๊ธฐ๋ฐ AI ์ธํฐ๋ทฐ ์คํธ๋ฆฌ๋ฐ ๋ฐ ๋ถ์ ๊ฒฐ๊ณผ ์๋ฆผ |
| ๐ ๋ฐ์ดํฐ ๋ถ์ | ํด๋ฌ์คํฐ๋ง, ๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ ์ง๊ณ ๋ฐ ๋์๋ณด๋ ๋ฐ์ดํฐ ์ ๊ณต |
flowchart TB
subgraph CLIENT["๐ฅ๏ธ React Client"]
WEB[Web Browser]
end
subgraph MAIN_SERVER["โ Spring Boot Server"]
AUTH[Auth API]
SURVEY[Survey API]
INTERVIEW[Interview API]
STREAMING[Streaming API]
ANALYTICS[Analytics API]
SSE_EMITTER[SSE Emitter]
end
subgraph AI_SERVER["๐ค FastAPI AI Server"]
RAG[RAG ์ง๋ฌธ ์์ฑ]
EMBEDDING[์๋ฒ ๋ฉ ๋ถ์]
CLUSTERING[HDBSCAN ํด๋ฌ์คํฐ๋ง]
end
subgraph AWS["โ๏ธ AWS Cloud"]
GAMELIFT[GameLift Streams]
S3[S3 Storage]
RDS[(MariaDB)]
end
WEB <-->|REST API| AUTH
WEB <-->|REST API| SURVEY
WEB <-->|REST API| ANALYTICS
WEB <-.->|SSE Stream| SSE_EMITTER
INTERVIEW -->|HTTP| RAG
INTERVIEW -->|HTTP| EMBEDDING
ANALYTICS -->|HTTP| CLUSTERING
STREAMING -->|AWS SDK v2| GAMELIFT
SURVEY -->|Presigned URL| S3
MAIN_SERVER --> RDS
sequenceDiagram
participant C as Client
participant S as Spring Boot
participant AI as FastAPI
participant GL as GameLift
Note over C,GL: 1๏ธโฃ ์ค๋ฌธ ์์ฑ & ์คํธ๋ฆฌ๋ฐ ํ๋ก๋น์ ๋
C->>S: POST /surveys (์ค๋ฌธ ์์ฑ)
S->>AI: POST /questions/generate (RAG ์ง๋ฌธ ์์ฑ)
AI-->>S: ์์ฑ๋ ์ง๋ฌธ ๋ชฉ๋ก
C->>S: POST /streaming-resources (GameLift ํ๋ก๋น์ ๋)
S->>GL: CreateApplication + CreateStreamGroup
GL-->>S: Resource ARN
Note over C,GL: 2๏ธโฃ ํ
์คํฐ ํ๋ ์ด & AI ์ธํฐ๋ทฐ
C->>S: GET /interview/stream (SSE ์ฐ๊ฒฐ)
S-->>C: SSE: greeting
loop ๊ฐ ์ง๋ฌธ๋ง๋ค
S->>AI: POST /interview/probing (๋ต๋ณ ๋ถ์)
AI-->>S: ๊ผฌ๋ฆฌ์ง๋ฌธ or ๋ค์์ง๋ฌธ
S-->>C: SSE: question
end
S-->>C: SSE: complete
Note over C,GL: 3๏ธโฃ ๋ถ์ ๊ฒฐ๊ณผ ์์ง
S->>AI: POST /analytics/cluster (ํด๋ฌ์คํฐ๋ง)
AI-->>S: ํด๋ฌ์คํฐ + ๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ
S-->>C: SSE: analytics_refresh
| ๊ธฐ์ | ๋ฒ์ | ์ ํ ์ด์ |
|---|---|---|
| Java | 21 LTS | Virtual Threads ์ง์, ์ต์ LTS |
| Spring Boot | 3.5.9 | ์์ ์ ์ธ ์ํฐํ๋ผ์ด์ฆ ํ๋ ์์ํฌ |
| Gradle | Wrapper | ๋น ๋ฅธ ๋น๋, ์ ์ฐํ ์์กด์ฑ ๊ด๋ฆฌ |
| ๋ผ์ด๋ธ๋ฌ๋ฆฌ | ์ฉ๋ |
|---|---|
spring-boot-starter-web |
REST API ๊ฐ๋ฐ |
spring-boot-starter-data-jpa |
ORM (JPA/Hibernate) |
spring-boot-starter-security |
์ธ์ฆ/์ธ๊ฐ |
spring-boot-starter-webflux |
๋น๋๊ธฐ HTTP ํด๋ผ์ด์ธํธ (WebClient) |
spring-boot-starter-validation |
Bean Validation |
springdoc-openapi |
Swagger UI API ๋ฌธ์ํ |
jjwt |
JWT ํ ํฐ ์์ฑ/๊ฒ์ฆ |
shedlock |
๋ถ์ฐ ์ค์ผ์ค๋ง ๋ฝ (๋ค์ค ์ธ์คํด์ค ํ๊ฒฝ) |
| ์๋น์ค | SDK | ์ฉ๋ |
|---|---|---|
| GameLift Streams | gameliftstreams |
ํด๋ผ์ฐ๋ ๊ฒ์ ์คํธ๋ฆฌ๋ฐ |
| S3 | s3 |
๊ฒ์ ๋น๋ ํ์ผ ์ ์ฅ |
| STS | sts |
์์ ์๊ฒฉ ์ฆ๋ช ๋ฐ๊ธ |
| ํ๊ฒฝ | Database | ๋น๊ณ |
|---|---|---|
| local | H2 (In-Memory) | ๋น ๋ฅธ ๊ฐ๋ฐ ์ฌ์ดํด |
| dev/prod | MariaDB | AWS RDS |
๋ฌธ์ : AI ์๋ฒ์ ์๋ต ์๊ฐ์ด ์ ์ด~์์ญ ์ด์ ๋ฌํ๋ฉฐ, ํด๋ผ์ด์ธํธ๊ฐ ์ค์๊ฐ์ผ๋ก ์ธํฐ๋ทฐ ์งํ ์ํฉ์ ํ์ธํด์ผ ํจ
ํด๊ฒฐ ๋ฐฉ์:
- Server-Sent Events (SSE) ๋ฅผ ํ์ฉํ ๋จ๋ฐฉํฅ ์ค์๊ฐ ์คํธ๋ฆฌ๋ฐ ๊ตฌํ
SseEmitter๋ฅผ ํตํ ์ด๋ฒคํธ ํ์ ๋ณ ๋ถ๊ธฐ (greeting_continue,greeting_done,question,reaction,interview_complete)- ๊ผฌ๋ฆฌ์ง๋ฌธ(Probing) ์ ์ด: ์๋ฒ ์ฃผ๋์ ์ธํฐ๋ทฐ ์ํ ๊ด๋ฆฌ๋ก ๋ฌดํ ๋ฃจํ ๋ฐฉ์ง
๊ด๋ จ ์ปค๋ฐ:
- feat: SSE๊ธฐ๋ฐ ์ค์๊ฐ ์ค๋ฌธ ์งํ ๊ธฐ๋ฅ ๊ตฌํ (#17)
- feat: ์ธํฐ๋ทฐ SSE ์ด๋ฒคํธ์ ๊ผฌ๋ฆฌ์ง๋ฌธ ์ ์ด ํ๋ ์ถ๊ฐ (#92)
- fix: SSE ์ฐ๊ฒฐ ๋๊น(Race Condition) ๋ฐ ์ข๋น ์คํธ๋ฆผ ๋ฌธ์ ํด๊ฒฐ (#176)
๋ฌธ์ : ํ๋ ์ดํ ์คํธ ๊ธฐ๊ฐ์๋ง ํด๋ผ์ฐ๋ ๊ฒ์ ์คํธ๋ฆฌ๋ฐ์ด ํ์ํ๋ฉฐ, ๋น์ฉ ์ต์ ํ์ ๋น ๋ฅธ ํ๋ก๋น์ ๋์ด ํ์
ํด๊ฒฐ ๋ฐฉ์:
- JIT(Just-In-Time) ํ๋ก๋น์ ๋: ์ค๋ฌธ ํ์ฑํ ์์ ์ GameLift Application + StreamGroup ์์ฑ
- Two-Phase Transaction Pattern: DB ์ํ ๋ณ๊ฒฝ๊ณผ AWS API ํธ์ถ ๋ถ๋ฆฌ๋ก ์ผ๊ด์ฑ ๋ณด์ฅ
- ๋น๋๊ธฐ ์ค์ผ์ผ๋ง: ์ค์ผ์ผ์ /๋ค์ด ์์ฒญ์ ๋น๋๊ธฐ๋ก ์ฒ๋ฆฌํ์ฌ API ์๋ต ์๊ฐ ๋จ์ถ
- Race Condition ํด๊ฒฐ: ๋์ ํ ์คํฐ ์ ์ ์ ๋ฆฌ์์ค ํ ๋น ์ถฉ๋ ๋ฐฉ์ง
๊ด๋ จ ์ปค๋ฐ:
- feat: GameLift Streams ๊ธฐ๋ฐ ์จ๋๋งจ๋ ๋ฆฌ์์ค ํ ๋น ๋ฐ ํ
์คํฐ ์ ์ ํ๊ฒฝ ๊ตฌํ (#53)
- fix: ์คํธ๋ฆฌ๋ฐ ๋ฆฌ์์ค ์์ฑ ๋ ์ด์ค ์ปจ๋์
ํด๊ฒฐ (#114)
- refactor: Streaming ์๋น์ค DB ์ปค๋ฅ์
๊ณ ๊ฐ ๋ฐฉ์ง ๋ฐ ์์ ์ฑ ๊ฐ์ (#164)
๋ฌธ์ : ํด๋ผ์ด์ธํธ ์ฃผ๋ ์ํ ๊ด๋ฆฌ ์ ์ค๋ณต ์์ฒญ, ์ํ ๋ถ์ผ์น ๋ฌธ์ ๋ฐ์
ํด๊ฒฐ ๋ฐฉ์:
SurveySession์ํฐํฐ์ ํ์ฌ ์ง๋ฌธ ID, ์์, ํด ๋ฒํธ๋ฅผ ์๋ฒ์์ ๊ด๋ฆฌ- ์ํ ์ ์ด ๋ฉ์๋ (
connect(),disconnectStream(),complete()) ๋ก ๋น์ฆ๋์ค ๊ท์น ์บก์ํ - ์ข ๋ฃ๋ ์ธ์ ์ ๋ํ ์ถ๊ฐ ์์ฒญ ๊ฑฐ๋ถ๋ก ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ๋ณด์ฅ
๊ด๋ จ ์ปค๋ฐ:
- Fix: ์ข
๋ฃ๋ ์ธํฐ๋ทฐ ์ธ์
๋ณดํธ, ์๋ฒ ์ฃผ๋ ์ธํฐ๋ทฐ ์ํ ๊ด๋ฆฌํ๋๋ก ๊ฐ์ (#111)
- feat: ์ธํฐ๋ทฐ ์ธ์
์ด์ดํ๊ธฐ/์
๋ฐ์ดํธ ๊ธฐ๋ฅ ๊ตฌํ (#99)
๋ฌธ์ : AI ์๋ฒ ์๋ต ์ง์ฐ ๋ฐ ์ฅ์ ์ ์ ์ฒด ์์คํ ์ํฅ ์ต์ํ ํ์
ํด๊ฒฐ ๋ฐฉ์:
- WebClient ๊ธฐ๋ฐ ๋น๋๊ธฐ HTTP ํต์ (Reactor)
- ํ์์์ ์ค์ ๋ถ๋ฆฌ: ํ๊ฒฝ๋ณ์๋ก read-timeout ๊ด๋ฆฌ (
AI_CLIENT_READ_TIMEOUT) - Health Check API: AI ์๋ฒ ์ํ ๋ชจ๋ํฐ๋ง
- Graceful Degradation: AI ์ฅ์ ์ ๊ธฐ๋ณธ ์ง๋ฌธ ์ธํธ ์ ๊ณต
๊ด๋ จ ์ปค๋ฐ:
- chore: AI client read-timeout์ ํ๊ฒฝ๋ณ์๋ก ๋ถ๋ฆฌ (#144)
- feat: MockDataLoader๊ตฌํ ๋ฐ AI์๋ฒ HealthCheck ์ถ๊ฐ (#124)
๋ฌธ์ : ๋ค์ค ์ธ์คํด์ค ๋ฐฐํฌ ์ ์ค์ผ์ค๋ฌ ์ค๋ณต ์คํ ๋ฐฉ์ง
ํด๊ฒฐ ๋ฐฉ์:
- ShedLock + JDBC ๊ธฐ๋ฐ ๋ถ์ฐ ๋ฝ ๊ตฌํ
- ์คํธ๋ฆฌ๋ฐ ๋ฆฌ์์ค ์ ๋ฆฌ, ์ธ์ ํ์์์ ์ฒ๋ฆฌ ๋ฑ ์ฃผ๊ธฐ์ ์์ ์ ์ ์ฉ
src/main/java/com/playprobie/api/
โโโ domain/ # ๋๋ฉ์ธ๋ณ ํจํค์ง (DDD ๊ธฐ๋ฐ)
โ โโโ analytics/ # ๐ ๋ถ์ (ํด๋ฌ์คํฐ๋ง, ๊ฐ์ ๋ถ์)
โ โ โโโ api/ # Controller
โ โ โโโ application/ # Service
โ โ โโโ dao/ # Repository
โ โ โโโ domain/ # Entity, VO
โ โ โโโ dto/ # Request/Response DTO
โ โโโ auth/ # ๐ ์ธ์ฆ (JWT, OAuth)
โ โโโ game/ # ๐ฎ ๊ฒ์/๋น๋ ๊ด๋ฆฌ
โ โโโ interview/ # ๐ค AI ์ธํฐ๋ทฐ ์ธ์
โ โโโ replay/ # ๐น ํ๋ ์ด ๋ฆฌํ๋ ์ด
โ โโโ streaming/ # โ๏ธ GameLift ์คํธ๋ฆฌ๋ฐ
โ โโโ survey/ # ๐ ์ค๋ฌธ ์ค๊ณ/๊ด๋ฆฌ
โ โโโ user/ # ๐ค ์ฌ์ฉ์
โ โโโ workspace/ # ๐ข ์ํฌ์คํ์ด์ค
โโโ global/ # ๊ณตํต ๋ชจ๋
โ โโโ config/ # Security, WebClient ์ค์
โ โโโ domain/ # BaseTimeEntity ๋ฑ
โ โโโ error/ # ์์ธ ์ฒ๋ฆฌ (GlobalExceptionHandler)
โ โโโ converter/ # JPA Converter
โโโ infra/ # ์ธํ๋ผ ๊ณ์ธต
โโโ ai/ # FastAPI ํด๋ผ์ด์ธํธ
โโโ gamelift/ # AWS GameLift SDK ๋ํผ
โโโ sse/ # SSE ์ด๋ฒคํธ ๊ด๋ฆฌ
erDiagram
USER ||--o{ WORKSPACE_MEMBER : "belongs to"
WORKSPACE ||--o{ WORKSPACE_MEMBER : "has"
WORKSPACE ||--o{ GAME : "contains"
GAME ||--o{ GAME_BUILD : "has versions"
GAME ||--o{ SURVEY : "has"
SURVEY ||--o| STREAMING_RESOURCE : "uses"
SURVEY ||--o{ SURVEY_SESSION : "has sessions"
SURVEY ||--o{ FIXED_QUESTION : "has questions"
SURVEY_SESSION ||--o{ INTERVIEW_LOG : "records"
GAME_BUILD ||--o{ STREAMING_RESOURCE : "deployed to"
USER {
bigint user_id PK
varchar email UK
varchar name
timestamp created_at
}
WORKSPACE {
bigint workspace_id PK
varchar name
timestamp created_at
}
GAME {
bigint game_id PK
bigint workspace_id FK
varchar name
text description
}
SURVEY {
bigint survey_id PK
uuid survey_uuid UK
bigint game_id FK
varchar name
enum status "DRAFT|ACTIVE|CLOSED"
enum test_stage
text theme_priorities
timestamp start_at
timestamp end_at
}
SURVEY_SESSION {
bigint session_id PK
uuid session_uuid UK
bigint survey_id FK
enum status "CONNECTED|IN_PROGRESS|COMPLETED|DROPPED|TERMINATED"
bigint current_fixed_q_id
int current_turn_num
timestamp started_at
timestamp ended_at
}
STREAMING_RESOURCE {
bigint resource_id PK
uuid resource_uuid UK
bigint survey_id FK
bigint build_id FK
varchar aws_application_id
varchar aws_stream_group_id
enum status "CREATING|PROVISIONING|READY|ACTIVE|ERROR"
int current_capacity
int max_capacity
}
INTERVIEW_LOG {
bigint log_id PK
bigint session_id FK
enum question_type "FIXED|PROBING|INSIGHT"
text question_text
text answer_text
enum validity
enum quality
timestamp created_at
}
๊ฐ๋ฐ ์๋ฒ ์คํ ํ ์๋ URL์์ API ๋ช ์ธ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค:
http://localhost:8080/swagger-ui/index.html
| ๋๋ฉ์ธ | ๋ฉ์๋ | ์๋ํฌ์ธํธ | ์ค๋ช |
|---|---|---|---|
| Auth | POST | /auth/login |
๋ก๊ทธ์ธ (JWT ๋ฐ๊ธ) |
| Survey | POST | /surveys |
์ค๋ฌธ ์์ฑ |
| Survey | PATCH | /surveys/{id}/status |
์ค๋ฌธ ์ํ ๋ณ๊ฒฝ |
| Interview | GET | /surveys/{id}/sessions/{sessionId}/stream |
SSE ์ธํฐ๋ทฐ ์คํธ๋ฆผ |
| Interview | POST | /surveys/{id}/sessions/{sessionId}/answer |
๋ต๋ณ ์ ์ถ |
| Streaming | POST | /streaming-resources |
์คํธ๋ฆฌ๋ฐ ๋ฆฌ์์ค ์์ฑ |
| Analytics | GET | /surveys/{id}/analytics |
๋ถ์ ๊ฒฐ๊ณผ ์กฐํ |
- Java 21+
- Gradle 8.x (๋๋ Gradle Wrapper ์ฌ์ฉ)
# 1. ์ ์ฅ์ ํด๋ก
git clone https://github.com/PlayProbie/server.git
cd server
# 2. ๋ก์ปฌ ํ๊ฒฝ ์คํ (H2 DB)
./gradlew bootRun
# 3. ํ
์คํธ ์คํ
./gradlew test
# 4. ๋น๋
./gradlew build| ๋ณ์๋ช | ์ค๋ช | ๊ธฐ๋ณธ๊ฐ |
|---|---|---|
SPRING_PROFILES_ACTIVE |
ํ์ฑ ํ๋กํ์ผ | local |
AI_SERVER_URL |
FastAPI AI ์๋ฒ URL | http://localhost:8000 |
AI_CLIENT_READ_TIMEOUT |
AI ์๋ฒ ์ฝ๊ธฐ ํ์์์ (ms) | 30000 |
JWT_SECRET |
JWT ์๋ช ํค | - |
AWS_ACCESS_KEY_ID |
AWS ์ก์ธ์ค ํค | - |
AWS_SECRET_ACCESS_KEY |
AWS ์ํฌ๋ฆฟ ํค | - |
- Client Repository - React ํ๋ก ํธ์๋
- AI Server Repository - FastAPI AI ์๋ฒ (LangGraph, RAG)
- ์ฝ๋ ํฌ๋งท: Naver Eclipse Formatter (Spotless)
- ์ปค๋ฐ ๋ฉ์์ง: Conventional Commits (
feat:,fix:,refactor:,chore:) - ๋ธ๋์นญ ์ ๋ต:
feat/#issue,fix/#issue
Built with โ Spring Boot by PlayProbie Team