298 lines
9.2 KiB
Markdown
298 lines
9.2 KiB
Markdown
# 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 內執行:
|
||
|
||
1. **Send**:Caller → 冪等檢查 → 配額 → Insert pending → Render → Provider chain → Update sent/failed
|
||
2. **Enqueue**:同上至 Insert → ZADD Redis → 回傳 pending;RetryWorker 背景 ClaimDue → 投遞
|
||
3. **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
|
||
|
||
```mermaid
|
||
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(同步)
|
||
|
||
```mermaid
|
||
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(異步)
|
||
|
||
```mermaid
|
||
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 | 業務 email OTP |
|
||
| verify_phone | sms | 業務 phone OTP |
|
||
| verify_registration_email | email | 註冊 email OTP |
|
||
| step_up_email | email | Email step-up |
|
||
| step_up_phone | sms | Phone step-up |
|
||
| account_suspended | email | 帳號停權通知 |
|
||
| tenant_welcome | email | 租戶歡迎信 |
|
||
|
||
---
|
||
|
||
## 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 路徑:**
|
||
|
||
1. 在 `generate/api/` 定義路由
|
||
2. `make gen-api`
|
||
3. `internal/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 |
|