领域驱动设计中读模型能否含基础逻辑及与领域模型差异问询
关于读模型(Read Model)的两个核心问题解答
Great questions—let’s break these down one by one, since read models have very different responsibilities than domain entities, which is the key to answering your doubts.
1. 读模型是否需要添加校验构造函数?
读模型的核心职责是高效承载查询结果,它不需要像领域实体(Entity/Aggregate Model)那样维护业务规则或数据一致性——因为读模型的数据来源已经是经过领域层校验后的合法数据(比如数据库中存储的是EmployeeModel处理后的结果)。
所以:
- 从必要性来说:完全不需要给读模型加校验构造函数。你的
EmployeeModel已经负责了所有业务规则的校验,读模型只需要映射查询到的数据即可,重复校验只会增加不必要的维护成本(比如领域模型的校验规则修改时,读模型也要同步更新)。 - 从设计原则来说:读模型应该是“无行为的纯数据载体”,只暴露必要的属性,不要包含业务逻辑(包括校验)。
举个反例,你写的这个构造函数其实是多余的:
class EmployeeReadModel { public DateTime? EmploymentDate { get; set; } EmployeeReadModel(DateTime? employeeDate) { EmploymentDate = employeeDate?? throw new Exception(); } }
因为写入数据库的EmploymentDate已经被EmployeeModel校验过是非空的,读模型从数据库读取时根本不会遇到null的情况(除非是历史遗留数据,但这时候应该在数据迁移或查询层面处理,而不是读模型校验)。
2. 读模型的属性能否与领域模型存在差异(比如非空变可空)?
答案是完全可以,而且这是读模型设计的常见场景!
读模型的设计完全围绕查询需求,而不是业务规则。领域模型的字段可空性是为了保证业务逻辑的正确性(比如EmployeeModel的EmploymentDate非空,因为雇佣员工必须有入职日期),但读模型的字段可空性取决于你的查询场景:
- 比如你需要做一个历史数据报表,其中包含一些早期系统中未完善
EmploymentDate的遗留员工数据,这时候读模型把EmploymentDate设为可空DateTime?就非常合理。 - 再比如你的查询只需要展示员工的基本信息,不需要入职日期,甚至可以直接在读模型里去掉这个字段。
核心原则:读模型是查询的“视图”,它的结构、字段类型、可空性只需要满足当前查询的需求,不需要和领域模型严格一一对应。
额外实践建议
- 保持读模型的简洁:只包含查询需要的字段,去掉任何不必要的逻辑。
- 避免重复逻辑:所有业务规则和校验都交给领域模型处理,读模型只负责数据承载。
- 按需设计:根据不同的查询场景设计不同的读模型(比如一个用于员工列表查询,一个用于员工详情查询),不用追求统一的读模型结构。
内容的提问来源于stack exchange,提问作者Arie
相关产品推荐
相关产品推荐

