template-monorepo/internal/model/notification/README.md

178 lines
5.4 KiB
Markdown
Raw Permalink Normal View History

2026-05-20 09:32:22 +00:00
# Notification 模組
統一對外通知入口Email / SMS支援同步 `Send`、異步 `Enqueue` + Redis 重試 Worker、冪等、配額、DLQ。
> 規格書Data Dictionary `notifications` / `notification_dlq`、NotifyKind 一覽、API→ [`SDD.md`](./SDD.md)
2026-05-20 09:32:22 +00:00
---
## 測試(本機)
單一 CLI`cmd/notify-test`Makefile 包一層:
```bash
make deps-up # Mongo + Redis異步必備
make mongo-index # 建索引
make notify-test METHOD=<名稱> [TO=] [PHONE=] [MOCK=1]
```
完整 METHOD 表見 [`docs/notification-testing.md`](../../../docs/notification-testing.md)。
### 同步 vs 異步
| 方式 | UseCase | 何時完成 | 需要 Redis |
|------|---------|----------|:----------:|
| **同步** | `Notifier.Send` | 呼叫當下送完(或失敗) | 建議(冪等/配額) |
| **異步** | `Notifier.Enqueue` | 先寫 Mongo `pending`,由 **RetryWorker** 從 Redis 佇列取出再送 | **必須** |
異步流程:
```mermaid
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
```bash
# 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-test``email-enqueue` / `sms-enqueue` 時會**暫時啟動** process 內 RetryWorker`make run-dev` 相同邏輯),輪詢 Redis 佇列直到 Mongo 紀錄變 `sent` 或逾時(預設 45 秒,`-poll` 可改)。
**成功時輸出範例:**
```text
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
```bash
make run-dev # ServiceContext.StartWorkers → NotificationRetry
```
此時由 API其他 use case 呼叫 `Enqueue` 即可;本機若尚無 Notification HTTP仍用 `notify-test METHOD=email-enqueue` 觸發。
### 查 Mongo 確認異步結果
```bash
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
```bash
make notify-test METHOD=admin-dlq
```
### Redis 佇列 key
預設 zset`notif:retry:zset`(可由 `Notification.Async.QueueRedisKey` 覆寫example 為 `notification:queue`)。
### 同步對照(順便測)
```bash
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`
```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
2026-05-20 09:32:22 +00:00
已內建:`smtp_sender.go`、`ses_sender.go`。
1.`provider/email/` 實作 `Sender``Name`、`Sort`、`Send`)。
2026-05-20 09:32:22 +00:00
2.`config/config.go` 加設定,並在 `usecase/factory.go``collectEmailSenders` 註冊。
3.`provider/email/*_test.go`
### 新增 NotifyKind
1. `domain/enum/kind.go` 常數。
2. embed 模板 + `template/registry.go`
2026-05-20 09:32:22 +00:00
3. OTP 類業務呼叫時設 `DoNotPersistBody: true`
### 新增 HTTP API
1.`generate/api/` 定義路由。
2. `make gen-api`
2026-05-20 09:32:22 +00:00
3. `internal/logic` 只呼叫 `domain/usecase`,做 types ↔ DTO 映射。
---
## 相關文件
- [`docs/notification-testing.md`](../../../docs/notification-testing.md) — `notify-test` METHOD 速查
- [`docs/model.md`](../../../docs/model.md) — domain 分包、Redis/Mongo 生命週期
- [`docs/identity-member-design.md`](../../../docs/identity-member-design.md) §8 — 在跨模組流程中的角色
- [`etc/README.md`](../../../etc/README.md) — Gateway 設定
- [`internal/library/redis/README.md`](../../library/redis/README.md)
- [`internal/library/mongo/README.md`](../../library/mongo/README.md)