STOMP + Redis Pub/Sub ๊ธฐ๋ฐ์ ์ค์๊ฐ ๋ฉํฐ๋ฃธ ์ฑํ ์๋ฒ
์ฑํ
๋ฐฉ ํ๋ฉด
|
ํ์๊ฐ์
ํ๋ฉด
|
๋ก๊ทธ์ธ ํ๋ฉด
|
ํ ํฐ ๊ฒ์ฆ ํ๋ฉด
|
- STOMP ๊ธฐ๋ฐ ๋ฉํฐ๋ฃธ ์ค์๊ฐ ์ฑํ
- Redis Pub/Sub์ ํตํ ์๋ฒ ๊ฐ ๋ฉ์์ง ๋๊ธฐํ
- Docker + GitHub Actions ๊ธฐ๋ฐ ์๋ ๋ฐฐํฌ
- ์ฑํ ๊ธฐ๋ก ์ ์ฅ ๋ฐ ์กฐํ API ์ ๊ณต
- Image, PDF ๊ฐ์ ํ์ผ๋ ์ก์์ ๊ฐ๋ฅ
- JWT ์ธ์ฆ ๋ฐ WebSocket ์ธ์ฆ ์์ฑ
- ๋๊ท๋ชจ ํธ๋ํฝ ๋ถํ ํ ์คํธ ๋ฐ ์ต์ ํ
- ํ ์คํธ ์๋ํ (๋จ์ / ํตํฉ / E2E)
- ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง ๋ฐ ๋ก๊น ๊ฐ์
- kubernetes ๋์ ์ ํตํ ์น์์ผ ๋ชจ๋์ HPA
- WebSocket ๊ธฐ๋ฐ STOMP ๋ฉ์์ง
- ๋ฉํฐ ์ธ์คํด์ค ๊ฐ Redis Pub/Sub ๋ธ๋ก๋์บ์คํธ
- ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ ๊ด๋ฆฌ (์ ์ฅ/ํด์ฅ, Heartbeat ๋ฑ)
- ๋ฉ์์ง ์ ์ฅ ๋ฐ ์กฐํ API
- PostgreSQL ๊ธฐ๋ฐ ์์์ฑ ๊ด๋ฆฌ
- ๋ฐฉ ๋จ์ ๋ํ ๋ด์ญ ํ์ด์ง ์กฐํ
- ๊ฒ์ / ํํฐ๋ง / ์ญ์ ๊ธฐ๋ฅ ํ์ฅ ์์
- ํ์๊ฐ์ ๋ฐ ๋ก๊ทธ์ธ
- JWT ํ ํฐ ๋ฐ๊ธ / ๊ฒ์ฆ
- ํฅํ OAuth2, ์์ ๋ก๊ทธ์ธ ํ์ฅ ์์
- ๋ชจ์ ์์ฑ ๋ฐ ๊ด๋ฆฌ ๋ฑ์ ๊ธฐ๋ฅ ์ํ
- ๋ฉํฐ๋ฏธ๋์ด ์ ๋ก๋ ๋ฐ ๋ถ๋ฌ์ค๊ธฐ
- S3์ ์ ๋ก๋ ํ, ์์ url๋ก ๋ถ๋ฌ์ค๊ธฐ ๊ฐ๋ฅ
- ๋ธ๋์น๋ช
:
feat/๊ธฐ๋ฅ๋ช - ๊ฐ ๊ธฐ๋ฅ์
feat/๊ธฐ๋ฅ๋ช๋ธ๋์น์์ ๊ฐ๋ฐ - ์๋ฃ ํ
main๋ธ๋์น๋ก PR ์์ฑ
- ๋์ ๋ธ๋์น:
main - ๋ชจ๋ PR์
main์ ๊ธฐ์ค์ผ๋ก ์์ฑ - ์ฝ๋ ๋ฆฌ๋ทฐ ๋ฐ ํ ์คํธ ์๋ฃ ํ ๋จธ์ง
- ๋ฐฐํฌ์ฉ ๋ธ๋์น:
production mainโproduction์ผ๋ก ๋จธ์ง ์ ๋ฐฐํฌ ์งํ- ํ๊ฒฝ ์ค์ ์ด๋ ๊ธด๊ธ ์์ ์
production์์ ์ง์ ๊ฐ๋ฅ - ๋จ,
production๋ณ๊ฒฝ์ฌํญ์ ๋ฐ๋์main์ผ๋ก PR ๋ฐ์
1๏ธโฃ feat/๊ธฐ๋ฅ๋ช
โโถ main # ๊ธฐ๋ฅ ๊ฐ๋ฐ ํ PR ๋ฐ ๋จธ์ง
2๏ธโฃ main โโถ production # ๋ฐฐํฌ ๋ฐ ์คํ๊ฒฝ ํ
์คํธ
3๏ธโฃ production โโถ main # ๋ฐฐํฌ ๊ด๋ จ ์์ ์ฌํญ ๋ฐ์
4๏ธโฃ production # ๋ฐฐํฌ ์คํ
- GitHub Actions ๊ธฐ๋ฐ
main๋๋productionํธ์ ์ ์๋ ๋น๋ & ๋ฐฐํฌ
- Gradle๋ก
chat-ws,chat-historyJAR ์์ฑ - Docker ์ด๋ฏธ์ง ๋น๋ ํ GitHub Container Registry๋ก ํธ์
(ํ๊ทธ:
latest,commit SHA)
- ์ด์ ์๋ฒ์ SSH ์ ์
- Docker Compose(
base + prod)๋ก ์๋น์ค ๋ฌด์ค๋จ ์ ๋ฐ์ดํธ - Postgres, Redis ๋ฑ ์ํ ์๋น์ค๋ ์ ์ง๋จ
-
GitHub Secrets์ ํตํด ๋ค์ ์ ๋ณด ์๋ ์ฃผ์
- API ๊ฒฝ๋ก
- ์ธ์ฆ ํ ํฐ
- CORS ํ์ฉ ๋๋ฉ์ธ ๋ฑ
| ๋ชฉ์ | ํ์ผ ๊ฒฝ๋ก |
|---|---|
| CI/CD ์ํฌํ๋ก์ฐ | .github/workflows/deploy.yml |
| Docker Compose (๊ธฐ๋ณธ ์ค์ ) | infra/docker-compose.base.yml |
| Docker Compose (์ด์ ํ๊ฒฝ) | infra/docker-compose.prod.yml |
sequenceDiagram
autonumber
participant Client as ํด๋ผ์ด์ธํธ<br/>(์น ๋ธ๋ผ์ฐ์ )
participant WS as chat-ws<br/>(WebSocket ์๋ฒ)
participant Redis as Redis<br/>(Pub/Sub)
participant History as chat-history<br/>(๋ฉ์์ง ์ ์ฅ ์๋ฒ)
participant DB as PostgreSQL<br/>(๋ฐ์ดํฐ๋ฒ ์ด์ค)
Note over Client,DB: 1๏ธโฃ ์ด๊ธฐ ์ฐ๊ฒฐ ๋ฐ ํ์คํ ๋ฆฌ ๋ก๋
Client->>+WS: WebSocket ์ฐ๊ฒฐ (SockJS/STOMP)
WS-->>-Client: ์ฐ๊ฒฐ ํ์ธ (SessionConnected)
Client->>+WS: SUBSCRIBE /topic/{roomId}
WS-->>-Client: ๊ตฌ๋
ํ์ธ
Client->>+History: GET /{roomId}/messages
History->>+DB: SELECT * FROM chat_message<br/>WHERE room_id = ? AND is_deleted = false
DB-->>-History: ๊ณผ๊ฑฐ ๋ฉ์์ง ๋ชฉ๋ก
History-->>-Client: ChatMessagesReceiveDto<br/>(๊ณผ๊ฑฐ ๋ํ ๋ด์ญ)
Note over Client,DB: 2๏ธโฃ ๋ฉ์์ง ์ก์์ ํ๋ก์ฐ
Client->>+WS: SEND /app/chat<br/>ChatMessageSendDto {sender, content, roomId}
WS->>+Redis: PUBLISH chat-message<br/>ChatMessagePubSubDto {tempId, sender, content, roomId}
Note over Redis: Redis Pub/Sub์ผ๋ก<br/>๋ชจ๋ ์ธ์คํด์ค์ ๋ธ๋ก๋์บ์คํธ
Redis-->>WS: chat-message ์ด๋ฒคํธ
WS->>Client: SEND /topic/{roomId}<br/>ChatMessageReceiveDto {id: tempId, createdAt: null}
Note over Client: ์์ ID๋ก ๋ฉ์์ง ์ฆ์ ํ์<br/>(๋๊ด์ UI ์
๋ฐ์ดํธ)
Redis-->>+History: chat-message ์ด๋ฒคํธ<br/>RedisChatMessageSubscriber
History->>+DB: INSERT INTO chat_message<br/>(sender, content, room_id)
DB-->>-History: ์ ์ฅ ์๋ฃ<br/>(id, created_at, edited_at ์๋ ์์ฑ)
History->>+Redis: PUBLISH chat-timestamp<br/>ChatTimestampPubSubDto {id, tempId, createdAt, editedAt}
Redis-->>WS: chat-timestamp ์ด๋ฒคํธ
WS->>-Client: SEND /topic/{roomId}<br/>ChatTimestampReceiveDto {id, tempId, createdAt, editedAt}
Note over Client: tempId๋ฅผ ์ค์ id๋ก ๊ต์ฒด<br/>ํ์์คํฌํ ์
๋ฐ์ดํธ
Note over Client,DB: 3๏ธโฃ ๋ฉํฐ ์ธ์คํด์ค ๋๊ธฐํ
Redis-->>WS: chat-message ์ด๋ฒคํธ
Note over WS: ๋ค๋ฅธ ์ธ์คํด์ค์<br/>WebSocket ํด๋ผ์ด์ธํธ๋ค
WS->>Client: SEND /topic/{roomId}<br/>๋์ผํ ๋ฉ์์ง ๋ธ๋ก๋์บ์คํธ
Note over Client,DB: 4๏ธโฃ ์ฐ๊ฒฐ ์ข
๋ฃ
Client->>WS: UNSUBSCRIBE /topic/{roomId}
Client->>WS: DISCONNECT
WS-->>Client: SessionDisconnect ์ด๋ฒคํธ



