Skip to content

12 - 通知服务与事件聚合 ​

通知服务是多事件聚合(Event Aggregation)的典型应用——它监听多个 topic,将分散的事件信息整合为统一的用户通知。

多事件订阅 ​

通知服务同时订阅 3 个 topic(ecommerce/cmd/notification-service/main.go:50-52):

go
router.AddNoPublisherHandler("order_created_notify",
    events.TopicOrderCreated, sub, handler.HandleOrderCreated)
router.AddNoPublisherHandler("payment_completed_notify",
    events.TopicPaymentCompleted, sub, handler.HandleOrderConfirmed)
router.AddNoPublisherHandler("order_cancelled_notify",
    events.TopicOrderCancelled, sub, handler.HandleOrderCancelled)

这种模式的优点是:新增通知类型只需添加新的 Handler,不影响已有通知逻辑。

通知处理器 ​

ecommerce/internal/notification/biz/event_handler.go 实现了三种通知:

go
func (h *NotificationHandler) HandleOrderCreated(msg *message.Message) error {
    var event eventsv1.OrderCreated
    proto.Unmarshal(msg.Payload, &event)
    h.log.Info(fmt.Sprintf("通知: 用户 %s 的订单 %s 已创建", event.UserId, event.OrderId))
    return nil
}

func (h *NotificationHandler) HandleOrderConfirmed(msg *message.Message) error {
    var event eventsv1.PaymentCompleted
    proto.Unmarshal(msg.Payload, &event)
    h.log.Info(fmt.Sprintf("通知: 订单 %s 支付成功", event.OrderId))
    return nil
}

func (h *NotificationHandler) HandleOrderCancelled(msg *message.Message) error {
    var event eventsv1.OrderCancelled
    proto.Unmarshal(msg.Payload, &event)
    h.log.Info(fmt.Sprintf("通知: 订单 %s 已取消,原因: %s", event.OrderId, event.Reason))
    return nil
}

从日志到多通道扩展 ​

当前实现仅通过 Zap logger 输出日志。实际系统中可以扩展为多通道通知:

mermaid
graph TD
    K[Kafka Events] --> N{通知服务}
    N --> SMS[短信通知]
    N --> PUSH[App Push]
    N --> EMAIL[邮件通知]
    N --> WS[WebSocket 推送]

扩展方式非常自然——每个 Handler 内部调用不同通知通道的实现,事件处理函数签名不变。推荐将通知通道抽象为接口:

go
type Notifier interface {
    Send(ctx context.Context, userID string, title string, body string) error
}

FanOut 模式应用 ​

通知服务本身就是 FanOut 的实际应用——同一个事件(如 PaymentCompleted)被多个 Handler 订阅:

事件order-servicenotification-service
OrderCreated✓
PaymentCompleted✓ (更新状态)✓ (发送通知)
OrderCancelled✓

在 Watermill 中实现这种 FanOut 非常自然——多个 AddNoPublisherHandler 订阅同一 topic 即可,Router 内部自动管理多条消息副本的传递。

异步处理模型 ​

通知服务与支付服务不同——它只消费事件,不发布新事件。这是"事件终点"(Event Sink)模式:

Kafka → Router → Handler → 通知给用户(日志/短信/Push)

Router 的并发控制(HandlerMaxConcurrency)在这里非常有用。如果短信接口 QPS 有限,可通过减小并发数防止服务被限流。当前默认并发 1,保证通知按事件时间顺序发送。

错误处理 ​

通知失败通常不应阻塞业务流程。建议策略:

  • 日志告警:错误记录到 Zap logger,触发监控告警
  • 不返回 error:Handler 返回 nil,Router 正常 ACK,避免消息堆积
  • 异步重试:通过 sidecar 进程或独立重试队列处理失败通知

这种"尽力通知"(Best-Effort Notification)策略确保通知失败不影响订单、库存、支付等核心业务流程。