← 中文资源

简体中文版本

构建多渠道通知分发服务

一个完整的通知分发服务:模板化消息、按渠道适配器、用户偏好、去重、投递跟踪与重试测试。

阅读英文原文

项目概述

本项目实现多渠道通知分发器:按用户首选渠道投递,对同一事件去重,重试瞬时失败,并记录每次投递尝试。通知功能在提供商失败、用户重复收到或发到从不查看的渠道之前,看起来只是一行调用。

架构与关键决策

分发器消费通知事件,解析用户渠道偏好,按渠道渲染模板,交给对应适配器;适配器接口刻意很窄——输入已渲染消息、输出分类后的结果——使假适配器可替代真实提供商,并阻止分发器被渠道分支撑大。每次尝试持久化渠道、响应与状态,使是否已通知有可查询的答案;去重在存储层强制,因为两个实例可能并发消费同一事件,只有共享键才能把竞态收敛为一次发送。

实施与测试

用可返回瞬时或永久失败的假适配器断言:重复事件只发送一次,瞬时失败退避重试,永久失败记录且不重试;断言偏好解析选中预期适配器;断言去重键在发送确认前写入。

限制与后续

  • 不支持静默时段与按通知类型的偏好。
  • 无应用内通知中心或已读状态。
  • 渠道内无提供商故障转移。

代码示例

pnpm init
pnpm add pg nodemailer express
pnpm add -D typescript tsx vitest
notification-dispatcher/
├── src/dispatch.ts
├── src/channels/email.ts
├── src/channels/webhook.ts
├── src/templates.ts
├── src/dedupe.ts
├── tests/dispatch.test.ts
└── package.json
interface Channel { send(msg: Rendered): Promise<{ id: string }>; }
const body = template(params).replace(/[<>&]/g, escapeHtml);
const channel = prefs[event.type] ?? user.defaultChannel;
const inserted = await db.insertIgnore('deliveries', { dedupKey });
if (!inserted) return;
await db.insertAttempt({ notificationId, channel, status, providerId });
pnpm vitest run