Skip to content

๐Ÿš€ [๊ธฐ๋Šฅ๊ฐœ์„ ][์•Œ๋ฆผ] FCM ํ† ํ”ฝ(Topic) ๊ธฐ๋ฐ˜ ์•Œ๋ฆผ ๊ตฌ๋…/๋ฐœ์†ก ๊ธฐ๋Šฅ ์ถ”๊ฐ€ย #623

@Chuseok22

Description

@Chuseok22

๐Ÿš€ [๊ธฐ๋Šฅ๊ฐœ์„ ][์•Œ๋ฆผ] FCM ํ† ํ”ฝ(Topic) ๊ธฐ๋ฐ˜ ์•Œ๋ฆผ ๊ตฌ๋…/๋ฐœ์†ก ๊ธฐ๋Šฅ ์ถ”๊ฐ€

๐Ÿ“ ํ˜„์žฌ ๋ฌธ์ œ์ 

ํ˜„์žฌ ์•Œ๋ฆผ์€ FcmToken(RedisHash)์— ์ €์žฅ๋œ ํ† ํฐ ๋‹จ์œ„๋กœ๋งŒ ๋ฐœ์†ก๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ์ƒ์œผ๋กœ๋Š” WebNotificationService๊ฐ€

  • ๋‹จ์ผ ์‚ฌ์šฉ์ž(sendToMember)
  • ์‚ฌ์šฉ์ž ๋ฆฌ์ŠคํŠธ(sendToMemberList)
  • ์ „์ฒด(sendToAll)

๋ฅผ ๋ชจ๋‘ โ€œํ† ํฐ ๋ฐ˜๋ณต(loop)โ€ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
์ด ๋ฐฉ์‹์€ ๋™์ผ ๊ด€์‹ฌ์‚ฌ๋ฅผ ๊ฐ€์ง„ ๋Œ€๊ทœ๋ชจ ์‚ฌ์šฉ์ž์—๊ฒŒ ์„ ํƒ์ /ํšจ์œจ์  ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ๊ฐ€ ์–ด๋ ต๊ณ , ํ† ํ”ฝ๋ณ„(์˜ˆ: ๊ณต์—ฐ ์นดํ…Œ๊ณ ๋ฆฌ, ์ง€์—ญ, ์•„ํ‹ฐ์ŠคํŠธ)๋กœ ๊ตฌ๋…/ํ•ด์ง€๋ฅผ ์ œ๊ณตํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์šด์˜ ๊ด€์ ์—์„œ โ€œ๊ตฌ๋…์ž ์ˆ˜, ๊ตฌ๋… ํ˜„ํ™ฉ, ์‹คํŒจ์œจโ€ ๋“ฑ์„ ์ฃผ์ œ๋ณ„๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ธฐ๊ฐ€ ์ œํ•œ์ ์ž…๋‹ˆ๋‹ค.

๐Ÿ› ๏ธ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ / ์ œ์•ˆ ๊ธฐ๋Šฅ

๋ชฉํ‘œ: FCM Topic์„ ํ™œ์šฉํ•ด โ€˜๊ตฌ๋…/ํ•ด์ง€ + ํ† ํ”ฝ ๋ฐœ์†กโ€™์„ ์ง€์›ํ•˜๊ณ , ๋Œ€๊ทœ๋ชจ/๊ณ ํšจ์œจ ์šด์˜์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ์„œ๋น„์Šค ๋ ˆ์ด์–ด์™€ ์ €์žฅ ๊ตฌ์กฐ๋ฅผ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

  1. ์„œ๋ฒ„ API ์„ค๊ณ„ (App/Web ๊ณต์šฉ)
  • POST /api/v1/notifications/topics/subscribe
    • Body: memberId, deviceType, topicName
    • ๊ธฐ๋Šฅ: ํ† ํฐ(๋“ค)์„ ํ•ด๋‹น ํ† ํ”ฝ์— ๊ตฌ๋…. (ํ† ํฐ ์กฐํšŒ: ๊ธฐ์กด FcmTokenService)
  • POST /api/v1/notifications/topics/unsubscribe
    • Body: memberId, deviceType, topicName
    • ๊ธฐ๋Šฅ: ํ† ํฐ(๋“ค)์„ ํ•ด๋‹น ํ† ํ”ฝ์—์„œ ํ•ด์ง€.
  • GET /api/v1/notifications/topics/my
    • Query: memberId
    • ๊ธฐ๋Šฅ: ์‚ฌ์šฉ์ž์˜ ํ˜„์žฌ ๊ตฌ๋… ํ† ํ”ฝ ๋ชฉ๋ก ์กฐํšŒ.
  • (๋‚ด๋ถ€/์šด์˜) POST /api/v1/notifications/topics/{topicName}/send
    • Body: NotificationPayload
    • ๊ธฐ๋Šฅ: ํ•ด๋‹น ํ† ํ”ฝ ๊ตฌ๋…์ž ์ „์ฒด์— ํ† ํ”ฝ ๋ฐœ์†ก.

๊ถŒํ•œ: topics/{topicName}/send๋Š” ๊ด€๋ฆฌ์ž/์šด์˜ ๊ถŒํ•œ๋งŒ ํ—ˆ์šฉ(RBAC ์ ์šฉ).

  1. ์„œ๋น„์Šค ๋ ˆ์ด์–ด ๋ฐ ์ฑ…์ž„ ๋ถ„๋ฆฌ (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๋Š” โ€œํ† ํฐ ์ง์ ‘ ๋ฐœ์†กโ€์— ์ง‘์ค‘ โ†’ ํ† ํ”ฝ ๋ฐœ์†ก์€ ๋ถ„๋ฆฌํ•˜์—ฌ ๋‹จ์ผ ์ฑ…์ž„ ์œ ์ง€.
  1. ๋ฐ์ดํ„ฐ ๋ชจ๋ธ๋ง/์ €์žฅ
  • ์˜ต์…˜ 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๊ฐ€ ์šด์˜/ํ†ต๊ณ„์— ์ ํ•ฉ.
  • ์ •ํ•ฉ์„ฑ: ์„œ๋ฒ„ ์ƒํƒœ์™€ FCM ์ƒํƒœ ๊ฐ„ ๋ถˆ์ผ์น˜ ์‹œ ์ฃผ๊ธฐ์  ๋ฆฌ์ปจ์‹ค๋ฆฌ์—์ด์…˜ ์žก(์„ ํƒ).
  1. ํ† ํ”ฝ ๋„ค์ด๋ฐ ๋ฐ ๊ฒ€์ฆ
  • ํ—ˆ์šฉ ๋ฌธ์ž/๊ธธ์ด ๋“ฑ FCM ๊ทœ์น™์— ๋งž๋Š” ํ† ํ”ฝ๋ช… ๋ฐธ๋ฆฌ๋ฐ์ด์…˜ ์ ์šฉ.
  • ์‚ฌ์ „ ์ •์˜๋œ ์—…๋ฌด ํ‘œ์ค€ ํ† ํ”ฝ ์Šคํ‚ค๋งˆ(์˜ˆ: artist.{id}, genre.{code}, region.{code}) ํ™•์ • ํ›„ ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ/ํŒจํ„ด ๊ฒ€์‚ฌ.
  1. ์šด์˜/๊ด€์ฐฐ ๊ฐ€๋Šฅ์„ฑ(Observability)
  • ๊ตฌ๋…/ํ•ด์ง€/๋ฐœ์†ก์— ๋Œ€ํ•ด ๊ตฌ์กฐํ™” ๋กœ๊ทธ, ๋ฉ”ํŠธ๋ฆญ(์š”์ฒญ ์ˆ˜, ์„ฑ๊ณต/์‹คํŒจ ์ˆ˜, ์ง€์—ฐ ์‹œ๊ฐ„), ์•Œ๋žŒ ์—ฐ๋™
  • ๋ถ€๋ถ„ ์‹คํŒจ ๋ฐœ์ƒ ์‹œ ์ƒ์„ธ ๋ฆฌํฌํŠธ(์‹คํŒจ ํ† ํฐ ๋ชฉ๋ก, ์‚ฌ์œ  ์ฝ”๋“œ) ์ €์žฅ
  1. ์—๋Ÿฌ ์ฒ˜๋ฆฌ/๋‚ด๊ฒฐํ•จ์„ฑ
  • ๋ถ€๋ถ„ ์‹คํŒจ ์‹œ ์žฌ์‹œ๋„ ์ „๋žต(์ง€์ˆ˜ ๋ฐฑ์˜คํ”„), Idempotency-Key(์š”์ฒญ ์ค‘๋ณต ๋ฐฉ์ง€), ์šด์˜์ž์šฉ ์žฌ์ฒ˜๋ฆฌ ์—”๋“œํฌ์ธํŠธ ์ œ๊ณต
  1. ๋ณด์•ˆ/๊ถŒํ•œ
  • ํ† ํ”ฝ ๋ฐœ์†ก ์—”๋“œํฌ์ธํŠธ๋Š” ๊ด€๋ฆฌ์ž ์—ญํ• ๋งŒ ํ—ˆ์šฉ, API ํ‚ค/๊ถŒํ•œ ํ† ํฐ ์š”๊ตฌ
  • ์‚ฌ์šฉ์ž์˜ memberId๋Š” ์ธ์ฆ ์ปจํ…์ŠคํŠธ์—์„œ ํ™•์ธ(์Šคํ‘ธํ•‘ ๋ฐฉ์ง€)
  1. ์ ์ง„์  ์ ์šฉ(๋งˆ์ด๊ทธ๋ ˆ์ด์…˜)
  • ์ดˆ๊ธฐ์—๋Š” ํŠน์ • ํ† ํ”ฝ(ํŒŒ์ผ๋Ÿฟ)์œผ๋กœ ์‹œ์ž‘ โ†’ ์•ˆ์ •ํ™” ํ›„ ๋ฒ”์œ„ ํ™•์žฅ
  • ๊ธฐ์กด โ€œ์ „์ฒด ๋ฐœ์†กโ€์€ ํ† ํ”ฝ all๋กœ ๋Œ€์ฒด ๊ฐ€๋Šฅ(์ดํ–‰ ๊ธฐ๊ฐ„ ๋ณ‘ํ–‰ ์šด์˜)

๐Ÿ™‹โ€โ™‚๏ธ ๋‹ด๋‹น์ž

  • ๋ฐฑ์—”๋“œ: @Chuseok22
  • ํ”„๋ก ํŠธ์—”๋“œ: @heesu52
  • ๋””์ž์ธ: ์ด๋ฆ„

Sub-issues

Metadata

Metadata

Assignees

Labels

enhancement๊ธฐ๋Šฅ ๊ฐœ์„ /ํ–ฅ์ƒ (Enhancement)๋ณด๋ฅ˜์ถ”ํ›„ ์ž‘์—… ์ง„ํ–‰์˜ˆ์ •

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions