|
|
||
|---|---|---|
| .. | ||
| config | ||
| domain | ||
| mock/repository | ||
| provider | ||
| repository | ||
| template | ||
| usecase | ||
| README.md | ||
| SDD.md | ||
| const.go | ||
| errors.go | ||
| redis.go | ||
README.md
Notification 模組
統一對外通知入口(Email / SMS),支援同步 Send、異步 Enqueue + Redis 重試 Worker、冪等、配額、DLQ。
測試(本機)
單一 CLI:cmd/notify-test,Makefile 包一層:
make deps-up # Mongo + Redis(異步必備)
make mongo-index # 建索引
make notify-test METHOD=<名稱> [TO=] [PHONE=] [MOCK=1]
完整 METHOD 表見 docs/notification-testing.md。
同步 vs 異步
| 方式 | UseCase | 何時完成 | 需要 Redis |
|---|---|---|---|
| 同步 | Notifier.Send |
呼叫當下送完(或失敗) | 建議(冪等/配額) |
| 異步 | Notifier.Enqueue |
先寫 Mongo pending,由 RetryWorker 從 Redis 佇列取出再送 |
必須 |
異步流程:
sequenceDiagram
participant App
participant Mongo
participant Redis
participant Worker
participant Provider
App->>Mongo: Insert status=pending
App->>Redis: ZADD 排程 job(立即或退避)
App-->>App: 回傳 pending DTO
Worker->>Redis: ClaimDue 到期 job
Worker->>Provider: Send 渲染後內容
Worker->>Mongo: Update status=sent / failed / retrying
Note over Worker,Mongo: 超過 MaxRetry → notification_dlq
測異步(Enqueue)
前置: etc/gateway.dev.yaml 要有 Mongo、Redis,且 Notification.Async 可維持預設(Worker ≥ 1)。
# Email 異步(歡迎信模板 tenant_welcome)
make notify-test METHOD=email-enqueue TO=you@example.com
# SMS 異步
make notify-test METHOD=sms-enqueue PHONE=0912345678
# mock:log 會印 [notification mock email/sms] 寄了什麼
make notify-test METHOD=email-enqueue TO=test@example.com MOCK=1
notify-test 在 email-enqueue / sms-enqueue 時會暫時啟動 process 內 RetryWorker(與 make run-dev 相同邏輯),輪詢 Redis 佇列直到 Mongo 紀錄變 sent 或逾時(預設 45 秒,-poll 可改)。
成功時輸出範例:
method=email-enqueue email=mock sms=mock
notification_id=... provider=mock status=sent
OK
失敗常見原因:
- 沒開 Redis →
async notification requires redis retry queue - Worker 沒跑、佇列卡住 →
timeout after 45s Email.From空白 → 啟動即失敗
關於 log: 若看到 FindOne - fail(mongo: no documents in result) 且 filter 含 idempotency_key,代表「第一次用這個 key 發送」,屬冪等查詢的正常結果,不是寄信失敗。新版 FindByIdempotency 已改用 Find 避免此誤導性 error log。
用 Gateway 長期跑 Worker(接近正式環境)
異步在 production 由 同一個 Gateway process 背景跑 Worker,不是只靠 CLI:
make run-dev # ServiceContext.StartWorkers → NotificationRetry
此時由 API/其他 use case 呼叫 Enqueue 即可;本機若尚無 Notification HTTP,仍用 notify-test METHOD=email-enqueue 觸發。
查 Mongo 確認異步結果
mongosh gateway --eval '
db.notifications.find({ tenant_id: "notify-test" })
.sort({ occurred_at: -1 })
.limit(3)
.forEach(d => print(d.kind, d.status, d.provider, d.attempts))
'
status |
意義 |
|---|---|
pending |
已 Enqueue,Worker 尚未送完 |
sent |
已送達 |
retrying |
失敗、等待退避重試 |
failed / dropped |
放棄或進 DLQ |
DLQ:
make notify-test METHOD=admin-dlq
Redis 佇列 key
預設 zset:notif:retry:zset(可由 Notification.Async.QueueRedisKey 覆寫,example 為 notification:queue)。
同步對照(順便測)
make notify-test METHOD=email-send TO=you@example.com MOCK=1
make notify-test METHOD=sms-send PHONE=0912345678 MOCK=1
設定摘要(gateway.dev.yaml)
Notification:
Async:
QueueRedisKey: notification:queue # 可省略,預設 notif:retry:zset
Worker: 2
MaxRetry: 5
BackoffSeconds: [1, 5, 30, 300, 1800]
Email:
From: noreply@example.com # 必填
SMTP:
Enable: true # 真實 SMTP;MOCK=1 時 CLI 會關閉
Email provider 看 SMTP.Enable / SES.Enable,不是 Provider: smtp 字串。
擴充指南
新增 Email Provider
已內建:smtp_sender.go、ses_sender.go。
- 在
provider/email/實作Sender(Name、Sort、Send)。 - 在
config/config.go加設定,並在usecase/factory.go的collectEmailSenders註冊。 - 補
provider/email/*_test.go。
新增 NotifyKind
domain/enum/kind.go常數。- embed 模板 +
template/registry.go。 - OTP 類業務呼叫時設
DoNotPersistBody: true。
新增 HTTP API
- 在
generate/api/定義路由。 make gen-api。internal/logic只呼叫domain/usecase,做 types ↔ DTO 映射。
相關文件
- docs/notification-testing.md —
notify-testMETHOD 速查 - docs/model.md — domain 分包、Redis/Mongo 生命週期
- docs/identity-member-design.md §11 — 產品設計
- etc/README.md — Gateway 設定
- internal/library/redis/README.md
- internal/library/mongo/README.md