4. 报表错误域建模:如何将报表错误抽象为领域模型、错误实体与值对象的设计原则
在 Ent 报表系统中,错误并非简单的字符串或状态码,而是一组具有业务含义、可追溯、可分类的领域概念。将错误抽象为领域模型,能够使错误处理逻辑与核心业务逻辑解耦,提升系统的可维护性与可测试性。本节将阐述如何围绕“错误实体”与“错误值对象”构建报表错误域模型。
4.1 错误域模型的核心分层
报表错误域模型遵循 DDD(领域驱动设计)的分层思想,将错误相关的概念划分为实体(Entity)与值对象(Value Object)。实体具有唯一标识,其状态会随时间变化;值对象则描述不可变的属性集合,通过属性值来定义其相等性。
核心原则: 错误实体负责记录错误的生命周期与上下文,错误值对象负责描述错误的类型、严重程度与定位信息。两者组合形成完整的错误领域模型。
4.2 错误实体(ReportError)设计原则
错误实体是报表错误域模型的核心,它承载了错误的完整生命周期。设计时需遵循以下原则:
- 唯一标识原则: 每个错误实体必须拥有全局唯一的标识(如 UUID),用于追踪、关联与去重。
- 状态机原则: 错误实体应包含明确的状态流转(如:已上报 → 确认中 → 处理中 → 已解决),禁止随意跳转。
- 上下文绑定原则: 错误实体必须关联到产生错误的报表实例、数据源或计算任务,确保可追溯。
- 不可变历史原则: 错误实体的状态变更应通过事件记录,而非直接修改字段,保留完整的审计轨迹。
| 属性 | 类型 | 说明 |
|---|---|---|
| ID | UUID | 全局唯一标识 |
| ReportID | UUID | 关联的报表实例 ID |
| Status | 枚举 | Reported / Confirmed / Processing / Resolved |
| CreatedAt | DateTime | 错误首次上报时间 |
| UpdatedAt | DateTime | 最后状态变更时间 |
| Version | int | 乐观锁版本号,用于并发控制 |
4.3 错误值对象(Value Object)设计原则
值对象用于描述错误的类型、严重程度与定位信息,它们是不可变的,且通过属性值判断相等性。设计时需遵循以下原则:
- 不可变性原则: 值对象一旦创建,其属性值不可修改。如需变更,应创建新的值对象实例。
- 自包含原则: 值对象应包含所有用于描述该概念的属性,不依赖外部上下文。
- 相等性基于属性原则: 两个值对象若所有属性值相同,则视为相等,而非通过 ID 判断。
- 无副作用原则: 值对象的方法不应修改自身或外部状态,仅返回新的值对象或计算结果。
设计提示: 将 ErrorType、Severity、ErrorLocation 设计为值对象,可以避免在多个实体中重复定义相同的错误分类逻辑,同时便于进行错误聚合统计与分类查询。
4.4 错误值对象分类与定义
报表错误域中常见的值对象包括以下三类:
| 值对象 | 属性 | 示例值 | 用途 |
|---|---|---|---|
| ErrorType | Category, SubCategory | DATA_SOURCE / CONNECTION_TIMEOUT | 错误分类,用于路由到不同的处理策略 |
| Severity | Level, Code | FATAL / 1, WARNING / 2, INFO / 3 | 错误严重程度,决定通知方式与处理优先级 |
| ErrorLocation | ReportID, SheetName, CellRef, FieldPath | rep-123, Sheet1, A1, user.name | 错误发生位置,用于快速定位与修复 |
4.5 错误聚合(ErrorAggregate)与领域服务
当多个错误实体之间存在关联关系时(如同一数据源导致的多个单元格错误),应引入错误聚合(ErrorAggregate)作为聚合根,统一管理这些错误实体的生命周期。聚合根负责保证聚合内数据的一致性。
// 错误聚合根示例(伪代码)
type ErrorAggregate struct {
ID uuid.UUID
RootCause string
Errors []*ReportError
Status AggregateStatus
}
// 领域服务:处理错误聚合
type ErrorResolutionService struct {
repo ErrorRepository
}
func (s *ErrorResolutionService) ResolveAggregate(aggID uuid.UUID) error {
agg, err := s.repo.LoadAggregate(aggID)
if err != nil {
return err
}
// 批量更新聚合内所有错误实体的状态
for _, errEntity := range agg.Errors {
errEntity.Resolve()
}
agg.Status = AggregateResolved
return s.repo.SaveAggregate(agg)
}
4.6 错误域模型的持久化策略
在 Ent 框架中,错误实体与值对象的持久化需遵循以下策略:
- 实体表: 为 ReportError 实体创建独立的数据表,包含 ID、状态、版本号等字段。
- 值对象嵌入: 值对象(如 ErrorType、Severity)通常作为 JSON 或复合字段嵌入实体表中,避免创建过多关联表。
- 事件表: 为错误状态变更创建独立的事件表,用于审计与回溯。
- 聚合表: 若使用聚合根,则创建聚合表,并通过外键关联子错误实体。
注意: 值对象嵌入实体表时,需确保序列化/反序列化的兼容性。建议使用 Protobuf 或 Avro 等 Schema 演进友好的序列化方案,避免字段变更导致历史数据解析失败。
4.7 错误域模型设计检查清单
在完成错误域模型设计后,可使用以下清单进行自检:
- 每个错误实体是否拥有全局唯一的 UUID?
- 错误实体的状态流转是否定义了明确的合法路径?
- 值对象是否实现了不可变性(无 Setter,构造时赋值)?
- 值对象的相等性判断是否基于所有属性,而非引用?
- 错误聚合根是否保证了聚合内实体的一致性?
- 持久化方案是否支持历史事件的回溯与审计?
- 错误类型与严重程度的分类是否覆盖了所有已知的报表错误场景?
通过以上设计原则与检查清单,可以构建出健壮、可扩展的报表错误域模型,为后续的错误处理、通知与修复提供坚实的领域基础。