cqrs中dto与领域模型必须物理隔离,禁止用copier.copy映射;dto需按消费端契约手写定义字段名、类型及嵌套结构,投影仅限异步projectionservice中显式、幂等执行。

别用 copier.Copy 做 DTO 与领域模型之间的转换——它在 CQRS 场景下是反模式,不是“省事”,而是埋雷。
为什么 copier.Copy 在读写分离架构里会出问题
常见错误现象:copier.Copy(&dto, &user) 看似一行搞定,实际导致:
- 字段语义错位:写模型的
UpdatedAt time.Time被直接拷成读模型的UpdatedAT int64,类型不匹配 panic 或精度丢失 - 敏感字段泄露:
PasswordHash string没被忽略,DTO 一序列化就发给前端 - 嵌套结构失控:领域模型有
Address *AddressEntity,DTO 要展平为AddrStreet string和AddrCity string,copier无法表达这种投影逻辑 - 性能假象:每次查询触发反射 + 大量内存分配,压测时 GC 飙升,QPS 断崖下跌
DTO 必须手写,且字段要按消费端契约定义
DTO 不是模型副本,而是接口契约。它字段名、类型、空值语义全由调用方决定:
- 前端要
avatar_url字符串,你就定义AvatarURL string,不是从 User 模型里“抄”一个Avatar string - 需要
LastLoginAt int64(秒级时间戳),就显式声明,不接受time.Time—— 否则 JSON 序列化行为不可控 - 所有字段必须显式列出,禁止用
struct{ *UserModel }匿名嵌入,否则PasswordHash、CreatedAt全暴露 - DTO 结构体里不能含
context.Context、*sql.Tx、函数或interface{}—— 这些无法 JSON 编码,打日志也会 panic
ProjectionService 是唯一允许做映射的地方
读写模型物理隔离,映射只能发生在异步 Projection 层,且必须显式、幂等:
- 不允许在 HTTP handler 或 query service 里调用
copier.Copy或mapstructure.Decode - ProjectionService 示例逻辑:监听 user.created 事件 → 查 Redis 获取 VIP 状态 → JOIN MySQL 补充地址信息 → 构造
UserSummaryDTO→ 写入读库 - 该过程必须可重放:同一事件多次投递,结果一致;失败后重试不产生脏数据
- 如果 DTO 字段来自计算(如
IsVip bool),必须在这里查依赖、算逻辑,而不是“从 User 拷贝过来”
真正难的不是写几行复制代码,而是守住边界:命令模型只管“改什么”,DTO 只管“给什么”。一旦混淆,后续加字段、改 API、做灰度就会变成连环排雷现场。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











