template-monorepo/internal/model/notification
王性驊 1f660b547a add fix eage case 2026-05-26 14:05:33 +08:00
..
config add notification and member modules with local dev stack 2026-05-20 15:01:08 +08:00
domain add notification and member modules with local dev stack 2026-05-20 15:01:08 +08:00
mock/repository add notification and member modules with local dev stack 2026-05-20 15:01:08 +08:00
provider add fix eage case 2026-05-26 14:05:33 +08:00
repository fix error msg 2026-05-20 17:32:22 +08:00
template add notification and member modules with local dev stack 2026-05-20 15:01:08 +08:00
usecase add fix eage case 2026-05-26 14:05:33 +08:00
README.md docs: 統整模組 README ↔ SDD 分工,砍重複內容 2026-05-22 17:18:08 +08:00
SDD.md add member totp 2026-05-22 07:52:39 +08:00
const.go add notification and member modules with local dev stack 2026-05-20 15:01:08 +08:00
errors.go add notification and member modules with local dev stack 2026-05-20 15:01:08 +08:00
redis.go add notification and member modules with local dev stack 2026-05-20 15:01:08 +08:00

README.md

Notification 模組

統一對外通知入口Email / SMS支援同步 Send、異步 Enqueue + Redis 重試 Worker、冪等、配額、DLQ。

規格書Data Dictionary notifications / notification_dlq、NotifyKind 一覽、APISDD.md


測試(本機)

單一 CLIcmd/notify-testMakefile 包一層:

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 要有 MongoRedis,且 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

# mocklog 會印 [notification mock email/sms] 寄了什麼
make notify-test METHOD=email-enqueue TO=test@example.com MOCK=1

notify-testemail-enqueue / sms-enqueue 時會暫時啟動 process 內 RetryWorkermake 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 已 EnqueueWorker 尚未送完
sent 已送達
retrying 失敗、等待退避重試
failed / dropped 放棄或進 DLQ

DLQ

make notify-test METHOD=admin-dlq

Redis 佇列 key

預設 zsetnotif: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              # 真實 SMTPMOCK=1 時 CLI 會關閉

Email provider 看 SMTP.Enable / SES.Enable,不是 Provider: smtp 字串。


擴充指南

新增 Email Provider

已內建:smtp_sender.goses_sender.go

  1. provider/email/ 實作 SenderNameSortSend)。
  2. config/config.go 加設定,並在 usecase/factory.gocollectEmailSenders 註冊。
  3. provider/email/*_test.go

新增 NotifyKind

  1. domain/enum/kind.go 常數。
  2. embed 模板 + template/registry.go
  3. OTP 類業務呼叫時設 DoNotPersistBody: true

新增 HTTP API

  1. generate/api/ 定義路由。
  2. make gen-api
  3. internal/logic 只呼叫 domain/usecase,做 types ↔ DTO 映射。

相關文件