9.2 KiB
9.2 KiB
Notification Service
Notification Service — SRS/SDD Document
| Version | Version Date | Editor | Memo |
|---|---|---|---|
| 1.0.0 | 2026/05/21 | Gateway Team | 初版:Gateway Notification 模組 SDD |
1. Introduction
1.1 Purpose
Notification 模組為 TianTing Gateway 的統一對外通知入口,支援 Email 與 SMS 的同步寄送、異步佇列、冪等、租戶配額、指數退避重試與 DLQ(Dead Letter Queue)管理。
1.2 Scope
範圍內:
- 同步
Send:渲染模板後立即送達 - 異步
Enqueue:Mongo 寫入 pending + Redis ZSET 排程 + RetryWorker 背景投遞 - 冪等(Redis cache + Mongo unique index)
- 租戶每日 Email/SMS 配額
- 模板渲染(embed HTML / SMS / Subject,多語系)
- Provider chain(SMTP、SES、Mitake、Mock)
- Admin DLQ 查詢與手動重試
範圍外:
- Push / Webhook(enum 已定義,尚未實作)
- 專用 HTTP API(目前僅內部 logic 呼叫;擴充路徑見 README)
1.3 Definitions, Acronyms, and Abbreviation
| 縮寫 | 說明 |
|---|---|
| NotifyKind | 通知類型(verify_email、tenant_welcome 等) |
| Channel | 通道:email / sms / push / webhook |
| DLQ | Dead Letter Queue,超過 MaxRetry 的失敗通知 |
| IdempotencyKey | 冪等鍵,同 tenant+kind+key 不重複寄送 |
| TargetHash | SHA-256(target),Mongo 不存明文 email/phone |
| RetryJob | Redis ZSET 成員,含 target(僅 Redis,不落 Mongo) |
1.4 Technologies to be used
| 項目 | 技術 |
|---|---|
| Application Language | Go 1.22+ |
| Framework | go-zero |
| Database | MongoDB(notifications、notification_dlq) |
| Cache / Queue | Redis(冪等、配額、retry ZSET) |
| Templates | go:embed + Go text/html template |
| Email Providers | SMTP、AWS SES、Mock |
| SMS Providers | Mitake(三竹)、Mock |
1.5 Overview
Notification 為 library-style domain package,嵌入 Gateway process 內執行:
- Send:Caller → 冪等檢查 → 配額 → Insert pending → Render → Provider chain → Update sent/failed
- Enqueue:同上至 Insert → ZADD Redis → 回傳 pending;RetryWorker 背景 ClaimDue → 投遞
- DLQ:attempts ≥ MaxRetry → status=dropped + 寫入 notification_dlq
2. System Overview
Notification 模組解耦業務邏輯與實際寄送:
- Auth 註冊 OTP、Member 業務驗證等 logic 層呼叫
Notifier.Send - 歡迎信等可容忍延遲的通知可走
Enqueue+ Worker - 隱私設計:Mongo / DLQ 只存
target_hash,明文 target 僅在 Redis RetryJob 生命週期內存在
Worker 由 ServiceContext.StartWorkers 啟動,與 Gateway 同 process 運行。
3. System Architecture
3.1 System Architecture
flowchart TB
subgraph Callers["Internal Callers"]
AuthLogic[logic/auth]
MemberLogic[logic/member]
end
subgraph NotificationModule["internal/model/notification"]
Notifier[NotifierUseCase]
Admin[AdminNotifierUseCase]
Worker[RetryWorker]
Factory[Provider Factory]
Renderer[Template Renderer]
end
subgraph Providers["Providers"]
EmailChain[email.Chain<br/>SMTP / SES / Mock]
SMSChain[sms.Chain<br/>Mitake / Mock]
end
subgraph Storage["Storage"]
Mongo[(MongoDB)]
Redis[(Redis)]
end
AuthLogic --> Notifier
MemberLogic --> Notifier
Notifier --> Renderer
Notifier --> Factory
Notifier --> Mongo
Notifier --> Redis
Factory --> EmailChain
Factory --> SMSChain
Worker --> Redis
Worker --> Mongo
Worker --> EmailChain
Worker --> SMSChain
Admin --> Mongo
Admin --> Redis
3.2 Decomposition Description
| 套件 | 職責 |
|---|---|
config/ |
Async、Rate、Email、SMS 設定 |
domain/entity/ |
Notification、NotificationDLQ |
domain/enum/ |
Channel、NotifyKind、NotifyStatus、Severity |
domain/repository/ |
Notification、DLQ、Store、RetryQueue 介面 |
domain/usecase/ |
Notifier、Admin 介面 + DTO |
domain/template/ |
模板 Spec、Renderer、Registry 介面 |
repository/ |
Mongo + Redis 實作 |
usecase/ |
Notifier、Admin、RetryWorker、factory、module |
provider/email/ |
SMTP、SES、Mock sender + chain |
provider/sms/ |
Mitake、Mock sender + chain |
template/ |
embed 模板 + render |
3.3 Send(同步)
sequenceDiagram
participant C as Caller
participant N as NotifierUseCase
participant R as Redis
participant M as Mongo
participant P as Provider
C->>N: Send(SendRequest)
N->>R: Get idempotency
alt cache hit
N-->>C: cached DTO
else miss
N->>M: FindByIdempotency / Insert pending
N->>R: Incr quota
N->>N: Render template
N->>P: Email/SMS chain.Send
N->>M: UpdateDelivery (sent/failed)
N->>R: Set idempotency cache
N-->>C: NotificationDTO
end
3.4 Enqueue + RetryWorker(異步)
sequenceDiagram
participant C as Caller
participant N as NotifierUseCase
participant M as Mongo
participant R as Redis
participant W as RetryWorker
participant P as Provider
C->>N: Enqueue(SendRequest)
N->>M: Insert (pending)
N->>R: ZADD RetryJob (score=now)
N-->>C: pending DTO
loop every 1s
W->>R: ClaimDue (ZREM)
W->>M: FindByID
W->>P: Render + deliver
alt success
W->>M: status=sent
else attempts < MaxRetry
W->>M: status=retrying
W->>R: ZADD (score=now+backoff)
else
W->>M: status=dropped
W->>M: Insert DLQ
end
end
退避序列(預設): [1, 5, 30, 300, 1800] 秒
4. Data Design
4.1 Data Dictionary
notifications(MongoDB)
| Field | Type | Comment | Index |
|---|---|---|---|
| _id | ObjectId | PK | PK |
| tenant_id | String | 租戶 ID | Unique(tenant_id, kind, idempotency_key) |
| uid | String | 會員 UID | (tenant_id, uid, occurred_at desc) |
| channel | String | email / sms | — |
| kind | String | NotifyKind | Unique(tenant_id, kind, idempotency_key) |
| target_hash | String | SHA-256(target) | — |
| template_key | String | 模板 key | — |
| locale | String | 語系 | — |
| body | String | 渲染後內容(OTP 類可省略) | — |
| provider | String | 實際使用的 provider | — |
| provider_message_id | String | 外部 message ID | — |
| status | String | pending/sent/failed/retrying/dropped | (status, attempts, occurred_at) |
| attempts | Int | 投遞嘗試次數 | — |
| last_error | String | 最後錯誤 | — |
| idempotency_key | String | 冪等鍵 | Unique(tenant_id, kind, idempotency_key) |
| severity | String | info/warn/critical | — |
| occurred_at | Int64 | 事件時間 ms | — |
| delivered_at | Int64 | 送達時間 ms | — |
| create_at | Int64 | 建立時間 ms | — |
| update_at | Int64 | 更新時間 ms | — |
notification_dlq(MongoDB)
| Field | Type | Comment | Index |
|---|---|---|---|
| _id | ObjectId | PK | PK |
| notification_id | String | 原 notification ID | — |
| tenant_id | String | 租戶 ID | — |
| uid | String | 會員 UID | — |
| channel | String | 通道 | — |
| kind | String | 通知類型 | — |
| target_hash | String | target hash | — |
| last_error | String | 最後錯誤 | — |
| attempts | Int | 總嘗試次數 | — |
| payload | Object | 重試 metadata(locale、data 等,不含 target) | — |
| occurred_at | Int64 | 事件時間 ms | — |
| create_at | Int64 | 建立時間 ms | — |
Redis Keys
| Key Pattern | 用途 | TTL |
|---|---|---|
| notif:idem:{tenant}:{kind}:{key} | 冪等 cache | 24h |
| notif:quota:{tenant}:{channel}:{yyyyMMdd} | 每日配額計數 | 25h |
| notif:retry:zset(可配置) | RetryJob ZSET(score=run_at_ms) | — |
NotifyKind 一覽
| Kind | 通道 | 說明 |
|---|---|---|
| verify_email | 業務 email OTP | |
| verify_phone | sms | 業務 phone OTP |
| verify_registration_email | 註冊 email OTP | |
| step_up_email | Email step-up | |
| step_up_phone | sms | Phone step-up |
| account_suspended | 帳號停權通知 | |
| tenant_welcome | 租戶歡迎信 |
5. API Design
現況: 尚無專用 HTTP API。Notification 由內部 logic 呼叫:
| 呼叫方 | 方法 | 用途 |
|---|---|---|
logic/auth/register_logic.go |
Notifier.Send |
註冊 email OTP |
logic/member/verify_helper.go |
Notifier.Send |
業務 email/phone OTP |
cmd/notify-test |
Send / Enqueue / Admin | 本機測試 CLI |
擴充 HTTP API 路徑:
- 在
generate/api/定義路由 make gen-apiinternal/logic映射 types ↔ DTO
6. Resource
| 資源 | 路徑 |
|---|---|
| 模組原始碼 | internal/model/notification/ |
| 開發 README | internal/model/notification/README.md |
| 測試指南 | docs/notification-testing.md |
| 測試 CLI | cmd/notify-test(make notify-test) |
| Worker | internal/worker/notification_retry/ |
| 設定範例 | etc/gateway.dev.example.yaml → Notification 區塊 |
| 設計參考 | docs/identity-member-design.md §11 |