领域事件必须在 domain 层发布,由聚合根或领域服务在状态变更后立即生成,仅写入内存通道或切片,不涉及序列化、网络调用或 Kafka/RabbitMQ 等基础设施;domain 层只定义事件结构和 Publisher 接口,具体实现由 application 层注入。

领域事件必须在 domain 层发布,且不能调用 Kafka/RabbitMQ
领域事件不是“发给消息队列的通知”,而是“业务事实已发生”的声明。它必须由 domain 层的聚合根或领域服务在状态变更后立即生成,比如 Order.Confirm() 执行成功后调用 o.publish(OrderConfirmed{ID: o.ID})。
这个 publish 方法只把事件塞进内存通道或切片,不涉及序列化、网络调用或任何中间件 SDK。常见错误是直接在 domain 层写 kafka.Producer.SendMessage 或 amqp.Publishing —— 这会让领域层强依赖基础设施,单元测试无法隔离,也违反分层原则。
-
domain层只定义事件 struct(如OrderPaidEvent)和Publisher接口(type Publisher interface { Publish(Event) }) - 具体实现(
KafkaPublisher、InMemoryPublisher、NATSPublisher)由application层提供并注入 - 测试时用内存实现替换,断言事件类型和字段即可,无需启动 Kafka 或 NATS
消费端幂等性不能靠消息队列去重,必须自己判重
Kafka 可能重复投递,NATS JetStream 也可能重传,“一次送达”不等于“一次处理”。消费者必须以 event.ID(建议用 ULID 或 UUIDv7)为唯一键,在 DB 或 Redis 中记录是否已处理。
不能用业务字段(如 order_id)单独做幂等键——同一订单可能触发多个事件(OrderCreated、OrderPaid),混在一起会误判。
立即学习“go语言免费学习笔记(深入)”;
- Redis 方案:
SETNX event_id:xxx 1 EX 3600,成功则处理,失败则跳过 - DB 方案:建表
event_consumed(event_id TEXT PRIMARY KEY, processed_at TIMESTAMPTZ),用INSERT ON CONFLICT DO NOTHING - 关键顺序:先
INSERT幂等记录,再执行业务逻辑;不能在事务里先查再发更新——若事务中途失败,状态未写入,下次又会重试
DB 提交和事件发送无法原子提交,别在事务里直接 publish
Go 没有两阶段提交支持,Kafka 事务也只能保证“写入 Kafka 和写入自身 topic 原子性”,无法涵盖你的 MySQL/PostgreSQL。若你在数据库事务内调用 kafka.Producer.SendMessage,会出现两种失败情况:
- DB 提交成功但消息发送失败 → 事件丢失
- 消息发送成功但 DB 回滚 → 事件幽灵(业务未发生,但下游已响应)
正确做法是“本地消息表 + 定时扫描”,或确保 Kafka 和业务表同属一个 Kafka cluster(2026年5月31日起部分云厂商支持跨服务事务协调器,但仍未普及)。
更现实的做法是:DB 提交成功后,用 goroutine 异步触发事件发送,并设置超时与重试;失败时写入本地 outbox 表供补偿任务拉取。
NATS/Kafka 客户端常见踩坑点
NATS 和 Kafka 是 Go 微服务最常用的事件总线,但客户端使用细节极易出错。
- NATS 发布必须调用
conn.Flush(),否则缓冲区数据可能未发出;conn.Status()要检查连接状态,避免静默丢弃 - Kafka 生产者必须设
PartitionKey(如order_id字节数组),否则同一业务实体事件被散列到不同分区,破坏顺序 - RabbitMQ 必须禁用
autoAck: true,改用msg.Ack()/msg.Nack()手动控制;amqp.Publishing{DeliveryMode: amqp.Persistent}生效前提是 Exchange 和 Queue 都声明为durable: true - 所有 JSON 序列化前确认结构体字段首字母大写、tag 拼写准确(
OrderID string `json:"order_id"`),否则json.Marshal返回空对象 {}
真正难的不是写对第一行代码,而是让每条事件在各种失败路径下都可追溯、可重放、不重复、不错乱——这需要从 domain 层契约开始,贯穿 infra 实现、消费逻辑和运维观测。


















