通知中心不能直接 new notifyservice(),因硬编码依赖导致测试困难、渠道扩展需修改多处、灰度开关失效;应定义窄接口 notifier,依赖注入具体实现,模板与渠道解耦,异步派发用带背压的队列,配置外置化并支持动态规则和 fallback。

通知中心为什么不能直接 new NotifyService()?
硬编码依赖会让测试、替换渠道、灰度开关全部失效。比如你写 notify := NewEmailNotify(),后续加短信、站内信就得改所有调用点。解耦的第一步是把具体实现和接口分离,用依赖注入代替直接实例化。
推荐做法:定义 Notifier 接口,所有渠道(邮件、短信、Webhook)都实现它;启动时通过配置或 DI 容器注入具体实现,而不是在业务逻辑里 new 任何具体类型。
- 接口定义要窄:只暴露
Send(ctx context.Context, msg *Notification) error,不暴露渠道特有字段(如SMTPHost) - 避免在接口里塞
SetTimeout()或RetryTimes()—— 这些属于实现细节,应由具体结构体自己管理 - 不要让
Notification结构体绑定 JSON 字段名或渠道字段(比如EmailSubject),统一用通用字段:Title、Content、Target(string 或 map[string]string)
如何让不同渠道复用同一份消息模板?
硬编码模板字符串(如 "Hi {{.Name}}, your order {{.ID}} shipped")会导致每加一个渠道就要复制粘贴一次。真正可维护的做法是把模板抽象成独立服务,并支持按渠道、语言、场景动态加载。
关键点在于:模板解析和渠道发送必须解耦。通知中心只传入 TemplateKey 和数据上下文,由模板引擎返回渲染后的 Title 和 Content,再交给对应 Notifier 发送。
- 模板存储建议用嵌入式文件(
embed.FS)+text/template,避免运行时读文件 IO 开销 - 模板键名格式建议为
"order.shipped.email.en",便于按channel.lang.scene组合查找 - 如果某渠道不支持富文本(如短信),模板引擎应自动 fallback 到纯文本版本,而不是让
SMSNotifier自己做降级处理
异步派发怎么避免丢消息又不拖慢主流程?
同步调用短信网关失败就等于用户收不到通知,但阻塞主流程(如下单成功响应)更不可接受。必须异步,但裸用 goroutine + channel 容易泄漏或堆积。
正确姿势是引入轻量级队列抽象:不是直接起 goroutine,而是把 *Notification 推到一个带缓冲、带重试、可监控的派发队列中。Go 标准库没有现成方案,但可用 github.com/ThreeDotsLabs/watermill 或自建基于 chan *Notification + worker pool 的简易队列。
- 缓冲 channel 长度别设太大(如
make(chan *Notification, 100)),否则 OOM 风险高;配合背压策略,满时返回ErrQueueFull - worker 数量别盲目设成
runtime.NumCPU()—— 短信渠道可能被限频,实际并发应按渠道配额控制(例如短信 ≤ 5 QPS) - 失败重试必须带退避(
time.AfterFunc(2*time.Second, ...)),且最大重试次数限制(如 3 次),避免死循环打挂下游
如何让运营能自助配置通知开关和渠道优先级?
每次改“订单创建是否发短信”都要发版,说明配置没外置。真正的解耦意味着业务代码完全不知道某个通知是否开启、走哪个渠道、是否需要审批。
核心是把决策逻辑从代码移到配置层:用 YAML 或数据库表定义 EventKey → Rules 映射,规则包含启用状态、渠道列表、条件表达式(如 user.level >= "vip")、降级链路(email → webhook → none)。
- 规则加载建议用
fsnotify监听 YAML 变更,避免重启生效 - 条件表达式别手写 parser,用现成的
github.com/Knetic/govaluate安全求值,输入限定为map[string]interface{} - 渠道优先级不是简单排序,而是“尝试第一个,失败后按顺序 fallback”,所以配置里要明确写
fallback_to: ["email", "webhook"],而非priority: 1
最常被忽略的是日志和追踪:每个通知实例必须带唯一 trace_id,贯穿模板渲染、渠道选择、发送、回调验证全流程。没有这个,出问题时根本分不清是模板错了还是短信网关超时了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











