-
Notifications
You must be signed in to change notification settings - Fork 2
Description
๐ [๊ธฐ๋ฅ๊ฐ์ ][์๋ฆผ] FCM ํ ํฝ(Topic) ๊ธฐ๋ฐ ์๋ฆผ ๊ตฌ๋ /๋ฐ์ก ๊ธฐ๋ฅ ์ถ๊ฐ
๐ ํ์ฌ ๋ฌธ์ ์
ํ์ฌ ์๋ฆผ์ FcmToken(RedisHash)์ ์ ์ฅ๋ ํ ํฐ ๋จ์๋ก๋ง ๋ฐ์ก๋๊ณ ์์ต๋๋ค. ์ฝ๋์์ผ๋ก๋ WebNotificationService๊ฐ
- ๋จ์ผ ์ฌ์ฉ์(
sendToMember) - ์ฌ์ฉ์ ๋ฆฌ์คํธ(
sendToMemberList) - ์ ์ฒด(
sendToAll)
๋ฅผ ๋ชจ๋ โํ ํฐ ๋ฐ๋ณต(loop)โ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค.
์ด ๋ฐฉ์์ ๋์ผ ๊ด์ฌ์ฌ๋ฅผ ๊ฐ์ง ๋๊ท๋ชจ ์ฌ์ฉ์์๊ฒ ์ ํ์ /ํจ์จ์ ๋ธ๋ก๋์บ์คํธ๊ฐ ์ด๋ ต๊ณ , ํ ํฝ๋ณ(์: ๊ณต์ฐ ์นดํ
๊ณ ๋ฆฌ, ์ง์ญ, ์ํฐ์คํธ)๋ก ๊ตฌ๋
/ํด์ง๋ฅผ ์ ๊ณตํ์ง ๋ชปํฉ๋๋ค. ๋ํ ์ด์ ๊ด์ ์์ โ๊ตฌ๋
์ ์, ๊ตฌ๋
ํํฉ, ์คํจ์จโ ๋ฑ์ ์ฃผ์ ๋ณ๋ก ๋ชจ๋ํฐ๋งํ๊ธฐ๊ฐ ์ ํ์ ์
๋๋ค.
๐ ๏ธ ํด๊ฒฐ ๋ฐฉ์ / ์ ์ ๊ธฐ๋ฅ
๋ชฉํ: FCM Topic์ ํ์ฉํด โ๊ตฌ๋ /ํด์ง + ํ ํฝ ๋ฐ์กโ์ ์ง์ํ๊ณ , ๋๊ท๋ชจ/๊ณ ํจ์จ ์ด์์ด ๊ฐ๋ฅํ๋๋ก ์๋น์ค ๋ ์ด์ด์ ์ ์ฅ ๊ตฌ์กฐ๋ฅผ ์ ๋ฆฌํฉ๋๋ค.
- ์๋ฒ API ์ค๊ณ (App/Web ๊ณต์ฉ)
POST /api/v1/notifications/topics/subscribe- Body:
memberId,deviceType,topicName - ๊ธฐ๋ฅ: ํ ํฐ(๋ค)์ ํด๋น ํ ํฝ์ ๊ตฌ๋
. (ํ ํฐ ์กฐํ: ๊ธฐ์กด
FcmTokenService)
- Body:
POST /api/v1/notifications/topics/unsubscribe- Body:
memberId,deviceType,topicName - ๊ธฐ๋ฅ: ํ ํฐ(๋ค)์ ํด๋น ํ ํฝ์์ ํด์ง.
- Body:
GET /api/v1/notifications/topics/my- Query:
memberId - ๊ธฐ๋ฅ: ์ฌ์ฉ์์ ํ์ฌ ๊ตฌ๋ ํ ํฝ ๋ชฉ๋ก ์กฐํ.
- Query:
- (๋ด๋ถ/์ด์)
POST /api/v1/notifications/topics/{topicName}/send- Body:
NotificationPayload - ๊ธฐ๋ฅ: ํด๋น ํ ํฝ ๊ตฌ๋ ์ ์ ์ฒด์ ํ ํฝ ๋ฐ์ก.
- Body:
๊ถํ:
topics/{topicName}/send๋ ๊ด๋ฆฌ์/์ด์ ๊ถํ๋ง ํ์ฉ(RBAC ์ ์ฉ).
- ์๋น์ค ๋ ์ด์ด ๋ฐ ์ฑ ์ ๋ถ๋ฆฌ (SRP)
TopicSubscriptionService(์ ๊ท)subscribe(memberId, deviceType, topicName)unsubscribe(memberId, deviceType, topicName)- ๋ด๋ถ์์
FcmTokenService๋ก ํ ํฐ ์กฐํ โFirebaseMessaging.subscribeToTopic(...) / unsubscribeFromTopic(...)ํธ์ถ - **๋ฐฐ์น ์ฒ๋ฆฌ(FCM ํ์ฉ ํ๋ ๋ด)**๋ก ์ฑ๋ฅ ์ต์ ํ ๋ฐ ๋ถ๋ถ ์คํจ ๋ฆฌํฌํธ ์์ง
TopicNotificationService(์ ๊ท)sendToTopic(topicName, NotificationPayload payload)- ๋ด๋ถ์์
Message.builder().setTopic(topicName)๊ตฌ์ฑ ํ ๋ฐ์ก - ์ ์ก ๊ฒฐ๊ณผ/์คํจ์จ ๋ฉํธ๋ฆญ ๊ธฐ๋ก
- ๊ธฐ์กด
WebNotificationService๋ โํ ํฐ ์ง์ ๋ฐ์กโ์ ์ง์ค โ ํ ํฝ ๋ฐ์ก์ ๋ถ๋ฆฌํ์ฌ ๋จ์ผ ์ฑ ์ ์ ์ง.
- ๋ฐ์ดํฐ ๋ชจ๋ธ๋ง/์ ์ฅ
- ์ต์
A (Lightweight): ์๋ฒ ์ธก ๊ตฌ๋
์ํ๋ ์ ์ฅํ์ง ์๊ณ , FCM ์ํ๋ง ์ ๋ขฐ.
- ์ฅ์ : ๊ตฌํ ๊ฐ๋จ, ์ ์ฅ์ ์ถ๊ฐ ๋ถํ์
- ๋จ์ : ๊ตฌ๋ ํํฉ/ํต๊ณ/๊ฐ์ฌ ๋ก๊ทธ ์ทจ์ฝ
- ์ต์
B (๊ถ์ฅ): ๋ก์ปฌ ์์ํ๋ก ๊ฐ์ฌ ๋ฐ ์ด์ ํธ์์ฑ ํ๋ณด
- RDB ํ
์ด๋ธ(์:
notification_topic_subscription) ์ ์id(PK),member_id(UUID),device_type,topic_name,fcm_token_id,subscribed_at,unsubscribed_at,is_active
- ์ฌ์ : ํ์ฌ
FcmToken์ Redis TTL ๊ตฌ์กฐ๋ก ๋จ๊ธฐ ์บ์ ์ฑ๊ฒฉ์ด ๊ฐํจ. ํ ํฝ ๊ตฌ๋ ์ ์ฅ๊ธฐ ์ํ์ด๋ฏ๋ก RDB๊ฐ ์ด์/ํต๊ณ์ ์ ํฉ.
- RDB ํ
์ด๋ธ(์:
- ์ ํฉ์ฑ: ์๋ฒ ์ํ์ FCM ์ํ ๊ฐ ๋ถ์ผ์น ์ ์ฃผ๊ธฐ์ ๋ฆฌ์ปจ์ค๋ฆฌ์์ด์ ์ก(์ ํ).
- ํ ํฝ ๋ค์ด๋ฐ ๋ฐ ๊ฒ์ฆ
- ํ์ฉ ๋ฌธ์/๊ธธ์ด ๋ฑ FCM ๊ท์น์ ๋ง๋ ํ ํฝ๋ช ๋ฐธ๋ฆฌ๋ฐ์ด์ ์ ์ฉ.
- ์ฌ์ ์ ์๋ ์
๋ฌด ํ์ค ํ ํฝ ์คํค๋ง(์:
artist.{id},genre.{code},region.{code}) ํ์ ํ ํ์ดํธ๋ฆฌ์คํธ/ํจํด ๊ฒ์ฌ.
- ์ด์/๊ด์ฐฐ ๊ฐ๋ฅ์ฑ(Observability)
- ๊ตฌ๋ /ํด์ง/๋ฐ์ก์ ๋ํด ๊ตฌ์กฐํ ๋ก๊ทธ, ๋ฉํธ๋ฆญ(์์ฒญ ์, ์ฑ๊ณต/์คํจ ์, ์ง์ฐ ์๊ฐ), ์๋ ์ฐ๋
- ๋ถ๋ถ ์คํจ ๋ฐ์ ์ ์์ธ ๋ฆฌํฌํธ(์คํจ ํ ํฐ ๋ชฉ๋ก, ์ฌ์ ์ฝ๋) ์ ์ฅ
- ์๋ฌ ์ฒ๋ฆฌ/๋ด๊ฒฐํจ์ฑ
- ๋ถ๋ถ ์คํจ ์ ์ฌ์๋ ์ ๋ต(์ง์ ๋ฐฑ์คํ), Idempotency-Key(์์ฒญ ์ค๋ณต ๋ฐฉ์ง), ์ด์์์ฉ ์ฌ์ฒ๋ฆฌ ์๋ํฌ์ธํธ ์ ๊ณต
- ๋ณด์/๊ถํ
- ํ ํฝ ๋ฐ์ก ์๋ํฌ์ธํธ๋ ๊ด๋ฆฌ์ ์ญํ ๋ง ํ์ฉ, API ํค/๊ถํ ํ ํฐ ์๊ตฌ
- ์ฌ์ฉ์์
memberId๋ ์ธ์ฆ ์ปจํ ์คํธ์์ ํ์ธ(์คํธํ ๋ฐฉ์ง)
- ์ ์ง์ ์ ์ฉ(๋ง์ด๊ทธ๋ ์ด์ )
- ์ด๊ธฐ์๋ ํน์ ํ ํฝ(ํ์ผ๋ฟ)์ผ๋ก ์์ โ ์์ ํ ํ ๋ฒ์ ํ์ฅ
- ๊ธฐ์กด โ์ ์ฒด ๋ฐ์กโ์ ํ ํฝ
all๋ก ๋์ฒด ๊ฐ๋ฅ(์ดํ ๊ธฐ๊ฐ ๋ณํ ์ด์)
๐โโ๏ธ ๋ด๋น์
- ๋ฐฑ์๋: @Chuseok22
- ํ๋ก ํธ์๋: @heesu52
- ๋์์ธ: ์ด๋ฆ