读写模型必须物理隔离,禁止用copier.copy等反射工具映射;dto需按消费端需求手写定义,字段名、类型、嵌套层级均由查询契约决定,与命令模型完全无关,且投影仅允许在异步projectionservice中显式、幂等执行。

读写模型不能映射,必须物理隔离——Go 里强行用 struct 转换、copier.Copy 或 mapstructure.Decode 做“命令模型 → 查询 DTO”是典型踩坑起点。
为什么不能用 copier.Copy 或 reflect.DeepEqual 做读写模型映射
常见错误是定义一个 User 领域模型,再搞个 UserView DTO,然后在 query handler 里写 copier.Copy(&view, &user)。问题在于:
- 字段语义错位:写模型的
UpdatedAt time.Time可能对应读模型的updated_at_ms int64,自动拷贝会丢精度或类型 panic - 敏感字段泄露:写模型含
PasswordHash字段,DTO 忘记忽略,HTTP 返回直接暴露 - 嵌套结构失控:领域模型有
Address *AddressEntity,DTO 要展平成AddrStreet,AddrCity,反射无法表达这种投影逻辑 - 性能假象:看似一行代码搞定,实际每次查询都触发反射+内存分配,压测时 GC 飙升
Query DTO 必须按前端/调用方需求手写,不是“从 domain 复制出来”
DTO 不是模型副本,而是**查询契约**。它字段名、嵌套层级、空值语义,全由消费端决定:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
UserListQuery的Page和PageSize是分页参数,和写模型无关;UserSummary返回的IsVip bool是计算字段(查 Redis + MySQL JOIN),不是从User结构体里抄来的 - 前端要
avatar_url,就定义AvatarURL string;要last_login_at时间戳秒级整数,就定义LastLoginAt int64,不接受time.Time - 所有 DTO 必须是 plain struct,禁止嵌入
context.Context、*sql.Tx、函数字段或 interface{} —— 否则无法 JSON 序列化、日志打点、gRPC 编码
命令模型(Command)字段必须精简且带校验,和 DTO 完全无关
CreateUserCommand 这类结构体只回答一个问题:“用户注册时,外部必须提供哪些最小必要数据?”
- 字段仅含
Email string `validate:"required,email"`、Password string `validate:"min=8"`、ReferrerID *int64—— 没有ID、CreatedAt、Version等写入后生成的字段 - 不包含任何与展示、统计、缓存相关的字段(比如
DisplayRole、FollowerCount),那些属于读模型职责 - 所有字段类型直白:用
string不用email.Address,用int64不用自定义UserID类型 —— 避免在 HTTP binding 层引入额外转换
投影服务(ProjectionService)才是唯一合法的“映射发生地”
如果真需要把写事件转成读模型数据,只能发生在异步投影层,且必须显式、幂等、可审计:
- 监听
UserCreatedEvent{ID: 123, Email: "a@b.c", CreatedAt: t},往读库插入:INSERT INTO user_profiles (id, email, created_at_sec) VALUES (123, 'a@b.c', EXTRACT(EPOCH FROM $2)::int) - 不能在 command handler 里同步调用
buildUserView(event);也不能让 query handler 反向查写库表再做转换 - 投影 SQL 或 Redis 命令必须手动编写,而不是依赖 ORM 的
Save()—— 因为读模型 schema 和写模型完全不同(宽表 vs 第三范式)
最常被忽略的一点:DTO 字段命名一旦发布给前端,就不可变更;而命令字段只要没发版,随时可删减。两者生命周期、演进节奏、owner 都不同——把它们当成“同一事物的两面”,是 Go CQRS 项目早期崩塌的主因。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










