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

178 lines
5.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Notification 模組
統一對外通知入口Email / SMS支援同步 `Send`、異步 `Enqueue` + Redis 重試 Worker、冪等、配額、DLQ。
> 規格書Data Dictionary `notifications` / `notification_dlq`、NotifyKind 一覽、API→ [`SDD.md`](./SDD.md)
---
## 測試(本機)
單一 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
已內建:`smtp_sender.go`、`ses_sender.go`。
1.`provider/email/` 實作 `Sender``Name`、`Sort`、`Send`)。
2.`config/config.go` 加設定,並在 `usecase/factory.go``collectEmailSenders` 註冊。
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 映射。
---
## 相關文件
- [`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)