Skip to content

Commit a93eab8

Browse files
JIWONKIMSclaude
andauthored
feat(be): RabbitMQ 인프라 구성 및 개발 환경 개선 (#141) (#151)
* feat(be): Switch to docker-compose for RabbitMQ deployment - Add docker-compose installation in Terraform user_data - Create infra/rabbitmq-docker-compose.yml as deployment template - Update main.tf to use docker-compose instead of docker run - Update local docker-compose.yml with proper network configuration - RabbitMQ container name: rabbitmq_1 for consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * refactor(infra): Move docker-compose installation to logical position - Moved docker-compose installation block right after Docker installation - Better script organization: Docker → docker-compose → containers - Removed duplicate installation code * docs(be): Update CLAUDE.md and fix dev RabbitMQ config - Add comprehensive RabbitMQ integration documentation - Add profile-based configuration strategy explanation - Add infrastructure & deployment section - Fix: Exclude RabbitMQ autoconfiguration in dev profile to prevent health check DOWN --------- Co-authored-by: Claude <[email protected]>
1 parent afa9d63 commit a93eab8

File tree

5 files changed

+241
-41
lines changed

5 files changed

+241
-41
lines changed

CLAUDE.md

Lines changed: 149 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ Korean Travel Guide Backend - A Spring Boot Kotlin application providing OAuth a
2424

2525
# Run specific test
2626
./gradlew test --tests "WeatherServiceTest"
27+
28+
# Compile Kotlin only (faster than full build)
29+
./gradlew compileKotlin
2730
```
2831

2932
### Code Quality
@@ -54,6 +57,24 @@ open http://localhost:8080/h2-console
5457
curl http://localhost:8080/actuator/health
5558
```
5659

60+
### Local RabbitMQ Setup
61+
```bash
62+
# Start RabbitMQ with docker-compose (for WebSocket testing)
63+
docker network create common
64+
docker-compose up -d
65+
66+
# Check RabbitMQ status
67+
docker logs rabbitmq-1
68+
69+
# Access RabbitMQ Management UI
70+
open http://localhost:15672
71+
# Username: admin
72+
# Password: (from .env PASSWORD_1 or default: qwpokd153098)
73+
74+
# Stop RabbitMQ
75+
docker-compose down
76+
```
77+
5778
### Redis (Optional - for caching)
5879
```bash
5980
# Start Redis with Docker
@@ -73,7 +94,7 @@ The codebase follows Domain-Driven Design with clear separation:
7394
```
7495
com/back/koreaTravelGuide/
7596
├── common/ # Shared infrastructure
76-
│ ├── config/ # App, Security, Redis, AI configs
97+
│ ├── config/ # App, Security, Redis, AI, DevConfig
7798
│ ├── security/ # OAuth2, JWT, filters
7899
│ ├── exception/ # Global exception handler
79100
│ └── ApiResponse.kt # Standard API response wrapper
@@ -86,7 +107,8 @@ com/back/koreaTravelGuide/
86107
│ │ └── tour/ # Tourism API integration
87108
│ ├── userChat/ # WebSocket chat between Guest-Guide
88109
│ │ ├── chatroom/ # Chat room management
89-
│ │ └── chatmessage/ # Message persistence
110+
│ │ ├── chatmessage/ # Message persistence & publishing
111+
│ │ └── stomp/ # WebSocket config (Simple/Rabbit)
90112
│ └── rate/ # Rating system for AI sessions & guides
91113
```
92114

@@ -95,15 +117,43 @@ com/back/koreaTravelGuide/
95117
1. **Each domain is self-contained** with its own entity, repository, service, controller, and DTOs
96118
2. **Common utilities live in `common/`** - never duplicate config or security logic
97119
3. **AI Chat uses Spring AI** with function calling for weather/tour tools
98-
4. **User Chat uses WebSocket** (STOMP) for real-time messaging
99-
5. **Global exception handling** via `GlobalExceptionHandler.kt` - just throw exceptions, they're caught automatically
120+
4. **User Chat uses WebSocket** with profile-based configuration:
121+
- **Dev**: SimpleBroker (in-memory, single server)
122+
- **Prod**: RabbitMQ STOMP Relay (scalable, multi-server)
123+
5. **Port-Adapter pattern** for message publishing (`ChatMessagePublisher` interface with `SimpleChatMessagePublisher` and `RabbitChatMessagePublisher` implementations)
124+
6. **Global exception handling** via `GlobalExceptionHandler.kt` - just throw exceptions, they're caught automatically
100125

101126
### Critical Configuration Files
102127

103128
- **build.gradle.kts**: Contains BuildConfig plugin that generates constants from YAML files (area-codes.yml, prompts.yml, etc.)
104-
- **application.yml**: Dev config with H2, Redis optional, OAuth2 providers
129+
- **application.yml**: Dev config with H2, Redis optional, OAuth2 providers, RabbitMQ for local testing
130+
- **application-prod.yml**: Production config with PostgreSQL, Redis required, RabbitMQ with connection stability settings
105131
- **SecurityConfig.kt**: Currently allows all requests for dev (MUST restrict for production)
106132
- **AiConfig.kt**: Spring AI ChatClient with OpenRouter (uses OPENROUTER_API_KEY env var)
133+
- **DevConfig.kt**: Auto-generates 2 dummy GUIDE users on startup (dev profile only)
134+
135+
### Profile-Based Configuration Strategy
136+
137+
The application uses Spring profiles (`@Profile` annotation) to switch implementations:
138+
139+
**Development Profile (`dev`):**
140+
- H2 in-memory database
141+
- `SimpleChatMessagePublisher` - uses Spring's SimpleBroker (no RabbitMQ needed)
142+
- `UserChatSimpleWebSocketConfig` - basic WebSocket with in-memory broker
143+
- Redis optional (session.store-type: none)
144+
- Dummy guide data auto-generation
145+
146+
**Production Profile (`prod`):**
147+
- PostgreSQL database
148+
- `RabbitChatMessagePublisher` - publishes to RabbitMQ
149+
- `UserChatRabbitWebSocketConfig` - STOMP Broker Relay to RabbitMQ
150+
- Redis required (session.store-type: redis)
151+
- Connection stability settings (timeouts, heartbeats)
152+
153+
When adding new features that differ between dev/prod, follow this pattern:
154+
1. Create an interface in the domain layer
155+
2. Create separate implementations with `@Profile("dev")` and `@Profile("prod")`
156+
3. Inject via the interface, Spring will wire the correct implementation
107157

108158
## Working with Spring AI
109159

@@ -121,6 +171,44 @@ fun getTourSpots(area: String): TourResponse
121171

122172
**Important**: System prompts are managed in `src/main/resources/prompts.yml` and compiled into BuildConfig at build time.
123173

174+
## WebSocket & Real-Time Messaging
175+
176+
### Architecture
177+
User-to-user chat uses WebSocket with STOMP protocol. The implementation switches based on profile:
178+
179+
**Development**: Uses Spring's SimpleBroker (in-memory)
180+
- Suitable for single-server development
181+
- No external dependencies
182+
- Messages stored in memory only
183+
184+
**Production**: Uses RabbitMQ STOMP Relay
185+
- Scales across multiple server instances
186+
- Messages persist in RabbitMQ
187+
- Handles reconnection and failover
188+
189+
### Message Flow
190+
1. Client connects to WebSocket endpoint: `/ws/userchat`
191+
2. Client sends message to: `/pub/chat/send`
192+
3. Server processes and publishes to: `/topic/chat/{roomId}`
193+
4. `ChatMessagePublisher` interface abstracts the publishing mechanism
194+
5. Messages are persisted to database via `ChatMessageService`
195+
196+
### RabbitMQ Configuration
197+
Located in `application.yml` and `application-prod.yml`:
198+
```yaml
199+
spring:
200+
rabbitmq:
201+
host: ${RABBITMQ_HOST}
202+
port: ${RABBITMQ_PORT}
203+
username: ${RABBITMQ_USERNAME}
204+
password: ${RABBITMQ_PASSWORD}
205+
stomp-port: ${RABBITMQ_STOMP_PORT} # Default: 61613
206+
```
207+
208+
RabbitMQ requires two plugins enabled:
209+
- `rabbitmq_management` - Management UI (port 15672)
210+
- `rabbitmq_stomp` - STOMP protocol support (port 61613)
211+
124212
## Testing Strategy
125213

126214
Tests are in `src/test/kotlin` mirroring the main structure:
@@ -152,7 +240,7 @@ Always run `./gradlew ktlintCheck` before committing - it's enforced by git hook
152240

153241
- **Development**: H2 in-memory (jdbc:h2:mem:testdb), resets on restart
154242
- **Production**: PostgreSQL (configured in application-prod.yml)
155-
- **JPA Strategy**: `ddl-auto: create-drop` in dev (wipes DB on restart)
243+
- **JPA Strategy**: `ddl-auto: create-drop` in dev (wipes DB on restart), `update` in prod
156244

157245
Main entities:
158246
- `User` - OAuth users with roles (GUEST/GUIDE/ADMIN)
@@ -166,7 +254,7 @@ Required `.env` file (copy from .env.example):
166254
```bash
167255
# AI (Required)
168256
OPENROUTER_API_KEY=sk-or-v1-...
169-
OPENROUTER_MODEL=anthropic/claude-3.5-sonnet
257+
OPENROUTER_MODEL=z-ai/glm-4.5-air:free
170258
171259
# OAuth (Required for auth)
172260
GOOGLE_CLIENT_ID=...
@@ -183,7 +271,14 @@ TOUR_API_KEY=...
183271
# JWT (Required for production)
184272
CUSTOM__JWT__SECRET_KEY=...
185273
186-
# Redis (Optional - caching)
274+
# RabbitMQ (Required for prod WebSocket)
275+
RABBITMQ_HOST=localhost
276+
RABBITMQ_PORT=5672
277+
RABBITMQ_USERNAME=admin
278+
RABBITMQ_PASSWORD=qwpokd153098
279+
RABBITMQ_STOMP_PORT=61613
280+
281+
# Redis (Optional in dev, required in prod)
187282
REDIS_HOST=localhost
188283
REDIS_PORT=6379
189284
REDIS_PASSWORD=
@@ -217,6 +312,33 @@ Common exceptions mapped to HTTP status:
217312
5. **Commit**: `{type}(scope): summary` (e.g., `feat(be): Add weather caching`)
218313
6. **PR title**: `{type}(scope): summary (#{issue})` (e.g., `feat(be): Add weather caching (#42)`)
219314

315+
## Infrastructure & Deployment
316+
317+
### Terraform Infrastructure (`infra/main.tf`)
318+
EC2 instance setup with:
319+
1. Docker & docker-compose installation
320+
2. Container network (`common`)
321+
3. Nginx Proxy Manager (ports 80, 443, 81)
322+
4. Redis (port 6379)
323+
5. PostgreSQL 16 (port 5432)
324+
6. RabbitMQ with management & STOMP plugins (ports 5672, 15672, 61613)
325+
326+
**Order matters**: Docker → docker-compose → network → containers
327+
328+
### CI/CD Pipeline (`.github/workflows/deploy.yml`)
329+
Blue-Green deployment strategy:
330+
1. Build on GitHub Actions runner
331+
2. Transfer JAR to EC2
332+
3. Deploy to blue/green container based on availability
333+
4. Health check before switching traffic
334+
5. Environment variables injected via `docker run -e`
335+
336+
### Docker Compose Files
337+
- `docker-compose.yml` (root) - Local development RabbitMQ
338+
- `infra/rabbitmq-docker-compose.yml` - EC2 production RabbitMQ template
339+
340+
Both require `common` network to be pre-created: `docker network create common`
341+
220342
## Common Issues & Solutions
221343

222344
### Build fails with "BuildConfig not found"
@@ -228,20 +350,33 @@ Common exceptions mapped to HTTP status:
228350
- Start Redis: `docker run -d -p 6379:6379 --name redis redis:alpine`
229351
- Check: `docker logs redis`
230352

353+
### RabbitMQ connection issues
354+
- In dev: RabbitMQ is optional (SimpleBroker used instead)
355+
- In prod: RabbitMQ is required for WebSocket
356+
- Start local RabbitMQ: `docker-compose up -d`
357+
- Check logs: `docker logs rabbitmq-1`
358+
- Verify plugins: `rabbitmq_management` and `rabbitmq_stomp` must be enabled
359+
231360
### ktlint failures
232361
- Auto-fix: `./gradlew ktlintFormat`
233362
- Pre-commit hook enforces this - setup via `./setup-git-hooks.sh`
234363

235364
### Spring AI errors
236365
- Verify `OPENROUTER_API_KEY` in .env
237-
- Check model name matches OpenRouter API (currently: anthropic/claude-3.5-sonnet)
366+
- Check model name matches OpenRouter API (currently: z-ai/glm-4.5-air:free)
238367
- Logs show AI requests: `logging.level.org.springframework.ai: DEBUG`
239368

240369
### OAuth login fails
241370
- Ensure all OAuth credentials in .env
242371
- Check redirect URIs match OAuth provider settings
243372
- Dev: `http://localhost:8080/login/oauth2/code/{provider}`
244373

374+
### WebSocket connection fails
375+
- Check profile: dev uses SimpleBroker, prod uses RabbitMQ
376+
- In prod: Ensure RabbitMQ is running and accessible
377+
- Verify STOMP port (61613) is open and reachable
378+
- Check `UserChatStompAuthChannelInterceptor` for authentication issues
379+
245380
## Important Notes
246381

247382
- **Never commit .env** - it's gitignored, contains secrets
@@ -250,3 +385,8 @@ Common exceptions mapped to HTTP status:
250385
- **Global config in common/** - don't duplicate security/config in domains
251386
- **BuildConfig is generated** - don't edit manually, modify YAML sources
252387
- **Redis is optional in dev** - required for production caching/sessions
388+
- **RabbitMQ is optional in dev** - required for production WebSocket scaling
389+
- **Profile-based beans** - use `@Profile` to switch implementations between dev/prod
390+
- **Dummy data in dev** - 2 guide users auto-generated on startup (DevConfig.kt)
391+
- **Docker network required** - `common` network must exist before running docker-compose
392+
- **Terraform order matters** - Docker installation before docker-compose, containers after both

docker-compose.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,26 @@ services:
44
rabbit:
55
image: rabbitmq:3-management # 버전 명시 권장 (예: 3.13-management)
66
container_name: rabbitmq-1
7+
networks:
8+
- common # 이미 존재하는 공용 네트워크 사용
79
ports:
810
- "5672:5672" # AMQP (Spring ↔ RabbitMQ)
911
- "61613:61613" # STOMP (Relay가 여기에 붙음)
1012
- "15672:15672" # Management UI (로컬에서만)
1113
environment:
14+
TZ: Asia/Seoul # 타임존 설정
1215
RABBITMQ_DEFAULT_USER: admin
13-
RABBITMQ_DEFAULT_PASS: admin
14-
volumes: # 영속화가 필요할 때만 유지
15-
- ./dockerProjects/rabbitmq-1/volumes/etc/rabbitmq:/etc/rabbitmq
16-
- ./dockerProjects/rabbitmq-1/volumes/var/lib/rabbitmq:/var/lib/rabbitmq
17-
- ./dockerProjects/rabbitmq-1/volumes/var/log/rabbitmq:/var/log/rabbitmq
16+
RABBITMQ_DEFAULT_PASS: "$PASSWORD_1"
17+
volumes:
18+
- ./volumes/etc/rabbitmq:/etc/rabbitmq
19+
- ./volumes/var/lib/rabbitmq:/var/lib/rabbitmq
20+
- ./volumes/var/log/rabbitmq:/var/log/rabbitmq
1821
command: >
1922
sh -c "
2023
rabbitmq-plugins enable rabbitmq_management &&
2124
rabbitmq-plugins enable rabbitmq_stomp &&
2225
rabbitmq-server
2326
"
27+
networks:
28+
common:
29+
external: true # 이미 만들어진 네트워크를 사용

infra/main.tf

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,13 @@ yum install docker -y
210210
systemctl enable docker
211211
systemctl start docker
212212
213+
# docker-compose 설치
214+
echo "docker-compose 설치 중..."
215+
curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
216+
chmod +x /usr/local/bin/docker-compose
217+
ln -sf /usr/local/bin/docker-compose /usr/bin/docker-compose
218+
docker-compose --version
219+
213220
# 도커 네트워크 생성
214221
docker network create common
215222
@@ -263,33 +270,48 @@ CREATE DATABASE \"${var.app_1_db_name}\" OWNER team11;
263270
GRANT ALL PRIVILEGES ON DATABASE \"${var.app_1_db_name}\" TO team11;
264271
"
265272
266-
# rabbitmq 설치
267-
docker run -d \
268-
--name rabbitmq_1 \
269-
--restart unless-stopped \
270-
--network common \
271-
-p 5672:5672 \
272-
-p 61613:61613 \
273-
-p 15672:15672 \
274-
-e RABBITMQ_DEFAULT_USER=admin \
275-
-e RABBITMQ_DEFAULT_PASS=${var.password_1} \
276-
-e TZ=Asia/Seoul \
277-
-v /dockerProjects/rabbitmq_1/volumes/data:/var/lib/rabbitmq \
278-
rabbitmq:3-management
279-
280-
# RabbitMQ가 준비될 때까지 대기
281-
echo "RabbitMQ가 기동될 때까지 대기 중..."
282-
until docker exec rabbitmq_1 rabbitmqctl status &> /dev/null; do
283-
echo "RabbitMQ가 아직 준비되지 않음. 5초 후 재시도..."
284-
sleep 5
285-
done
286-
echo "RabbitMQ가 준비됨. STOMP 플러그인 활성화 중..."
287-
288-
# RabbitMQ STOMP 플러그인 활성화
289-
docker exec rabbitmq_1 rabbitmq-plugins enable rabbitmq_stomp
290-
docker exec rabbitmq_1 rabbitmq-plugins enable rabbitmq_management
291-
292-
echo "RabbitMQ 설치 및 설정 완료!"
273+
# RabbitMQ docker-compose.yml 생성
274+
mkdir -p /dockerProjects/rabbitmq_1
275+
cat > /dockerProjects/rabbitmq_1/docker-compose.yml <<'RABBITMQ_COMPOSE'
276+
version: "3.8"
277+
278+
services:
279+
rabbitmq:
280+
image: rabbitmq:3-management
281+
container_name: rabbitmq_1
282+
restart: unless-stopped
283+
networks:
284+
- common
285+
ports:
286+
- "5672:5672"
287+
- "61613:61613"
288+
- "15672:15672"
289+
environment:
290+
TZ: Asia/Seoul
291+
RABBITMQ_DEFAULT_USER: admin
292+
RABBITMQ_DEFAULT_PASS: \${PASSWORD_1}
293+
volumes:
294+
- /dockerProjects/rabbitmq_1/volumes/etc/rabbitmq:/etc/rabbitmq
295+
- /dockerProjects/rabbitmq_1/volumes/var/lib/rabbitmq:/var/lib/rabbitmq
296+
- /dockerProjects/rabbitmq_1/volumes/var/log/rabbitmq:/var/log/rabbitmq
297+
command: >
298+
sh -c "
299+
rabbitmq-plugins enable rabbitmq_management &&
300+
rabbitmq-plugins enable rabbitmq_stomp &&
301+
rabbitmq-server
302+
"
303+
304+
networks:
305+
common:
306+
external: true
307+
RABBITMQ_COMPOSE
308+
309+
# RabbitMQ 시작
310+
echo "RabbitMQ 시작 중..."
311+
cd /dockerProjects/rabbitmq_1
312+
docker-compose up -d
313+
314+
echo "RabbitMQ docker-compose 설치 완료!"
293315
294316
echo "${var.github_access_token_1}" | docker login ghcr.io -u ${var.github_access_token_1_owner} --password-stdin
295317

0 commit comments

Comments
 (0)