4. 报表错误域建模:如何将报表错误抽象为领域模型、错误实体与值对象的设计原则

在 Ent 报表系统中,错误并非简单的字符串或状态码,而是一组具有业务含义、可追溯、可分类的领域概念。将错误抽象为领域模型,能够使错误处理逻辑与核心业务逻辑解耦,提升系统的可维护性与可测试性。本节将阐述如何围绕“错误实体”与“错误值对象”构建报表错误域模型。

4.1 错误域模型的核心分层

报表错误域模型遵循 DDD(领域驱动设计)的分层思想,将错误相关的概念划分为实体(Entity)与值对象(Value Object)。实体具有唯一标识,其状态会随时间变化;值对象则描述不可变的属性集合,通过属性值来定义其相等性。

核心原则: 错误实体负责记录错误的生命周期与上下文,错误值对象负责描述错误的类型、严重程度与定位信息。两者组合形成完整的错误领域模型。
报表错误域模型分层结构 实体层(Entity) ReportError ID: UUID 状态: 已上报/处理中/已解决 ErrorAggregate 聚合根: 错误聚合 包含多个错误实体 ErrorEvent 事件溯源 状态变更记录 值对象层(Value Object) ErrorType 数据源/计算/格式 Severity 致命/警告/提示 ErrorLocation 报表ID/单元格/字段路径 实体具有唯一标识(UUID),值对象通过属性值判断相等性,两者共同构成完整的错误领域模型

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 错误域模型设计检查清单

在完成错误域模型设计后,可使用以下清单进行自检:

  1. 每个错误实体是否拥有全局唯一的 UUID?
  2. 错误实体的状态流转是否定义了明确的合法路径?
  3. 值对象是否实现了不可变性(无 Setter,构造时赋值)?
  4. 值对象的相等性判断是否基于所有属性,而非引用?
  5. 错误聚合根是否保证了聚合内实体的一致性?
  6. 持久化方案是否支持历史事件的回溯与审计?
  7. 错误类型与严重程度的分类是否覆盖了所有已知的报表错误场景?

通过以上设计原则与检查清单,可以构建出健壮、可扩展的报表错误域模型,为后续的错误处理、通知与修复提供坚实的领域基础。