@@ -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
5457curl 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```
7495com/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/
951171 . ** Each domain is self-contained** with its own entity, repository, service, controller, and DTOs
961182 . ** Common utilities live in ` common/ ` ** - never duplicate config or security logic
971193 . ** 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
126214Tests 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
157245Main 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)
168256OPENROUTER_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)
172260GOOGLE_CLIENT_ID=...
@@ -183,7 +271,14 @@ TOUR_API_KEY=...
183271# JWT (Required for production)
184272CUSTOM__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)
187282REDIS_HOST=localhost
188283REDIS_PORT=6379
189284REDIS_PASSWORD=
@@ -217,6 +312,33 @@ Common exceptions mapped to HTTP status:
2173125. **Commit** : ` {type}(scope): summary` (e.g., `feat(be): Add weather caching`)
2183136. **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
0 commit comments