2. 报表领域模型设计:报表实体定义、报表值对象设计、报表聚合根建模、报表领域服务
领域模型是报表系统的核心骨架。在 Go 语言实现的 DDD 架构中,报表领域模型需要精确区分实体、值对象、聚合根与领域服务的职责边界。本节从这四个维度展开,建立可落地的报表领域模型设计范式。
2.1 报表实体定义
实体(Entity)拥有唯一标识,且其状态会随业务操作发生变化。在报表领域,报表定义(ReportDefinition) 是最核心的实体。
| 属性 | 类型 | 说明 |
|---|---|---|
| ReportID | string (UUID) | 全局唯一标识,实体身份锚点 |
| Name | string | 报表名称,业务可读 |
| DataSourceID | string | 关联的数据源标识 |
| Status | ReportStatus | 草稿/已发布/已归档 |
| Version | int | 乐观锁版本号,用于并发控制 |
| CreatedAt | time.Time | 创建时间 |
| UpdatedAt | time.Time | 最后修改时间 |
实体设计原则: 报表实体的
ReportID 在系统内不可变,即使报表名称或数据源变更,标识依然保持不变。Go 中建议使用 uuid.New() 生成,避免自增 ID 在分布式环境下的冲突风险。
// ReportDefinition 是报表领域的核心实体
type ReportDefinition struct {
ReportID string `json:"report_id"`
Name string `json:"name"`
DataSourceID string `json:"data_source_id"`
Status ReportStatus `json:"status"`
Version int `json:"version"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// ReportStatus 表示报表生命周期状态
type ReportStatus string
const (
ReportStatusDraft ReportStatus = "draft"
ReportStatusPublished ReportStatus = "published"
ReportStatusArchived ReportStatus = "archived"
)
2.2 报表值对象设计
值对象(Value Object)没有唯一标识,通过属性值来定义自身,且不可变。在报表模型中,查询参数(QueryParam) 和 报表样式(ReportStyle) 是典型的值对象。
| 值对象 | 属性 | 不可变规则 |
|---|---|---|
| QueryParam | StartDate, EndDate, Filters, SortBy | 创建后不可修改,替换即新建 |
| ReportStyle | Theme, FontSize, ColorScheme | 通过 With* 方法返回新实例 |
| ColumnDef | FieldName, DisplayName, Width, Align | 值相等即视为相同 |
Go 实现技巧: 值对象应实现
Equals() 方法用于比较,且所有字段导出为只读(首字母小写或使用 getter 方法)。避免暴露指针,防止外部修改。
// QueryParam 是不可变值对象
type QueryParam struct {
startDate time.Time
endDate time.Time
filters map[string]string
sortBy string
}
func NewQueryParam(start, end time.Time, filters map[string]string, sortBy string) QueryParam {
return QueryParam{
startDate: start,
endDate: end,
filters: filters,
sortBy: sortBy,
}
}
func (q QueryParam) Equals(other QueryParam) bool {
return q.startDate.Equal(other.startDate) &&
q.endDate.Equal(other.endDate) &&
reflect.DeepEqual(q.filters, other.filters) &&
q.sortBy == other.sortBy
}
2.3 报表聚合根建模
聚合根(Aggregate Root)是领域模型的边界控制点。在报表上下文中,报表聚合(ReportAggregate) 将实体与值对象组合为一个一致性单元,外部只能通过聚合根访问内部对象。
// ReportAggregate 是报表聚合根
type ReportAggregate struct {
report *ReportDefinition // 实体
query QueryParam // 值对象
style ReportStyle // 值对象
columns []ColumnDef // 值对象集合
}
func NewReportAggregate(report *ReportDefinition, query QueryParam, style ReportStyle, columns []ColumnDef) *ReportAggregate {
return &ReportAggregate{
report: report,
query: query,
style: style,
columns: columns,
}
}
// 聚合根提供业务方法,外部不能直接修改内部对象
func (a *ReportAggregate) Publish() error {
if a.report.Status != ReportStatusDraft {
return fmt.Errorf("only draft report can be published")
}
a.report.Status = ReportStatusPublished
a.report.UpdatedAt = time.Now()
return nil
}
聚合根设计避坑: 不要将数据库操作(如 Repository 调用)放在聚合根方法内。聚合根只负责业务规则校验和状态变更,持久化由领域服务或应用服务协调完成。
2.4 报表领域服务
领域服务(Domain Service)用于处理那些不适合放在实体或值对象中的业务逻辑,通常涉及多个聚合根协作或外部系统交互。
| 领域服务 | 职责 | 协作对象 |
|---|---|---|
| ReportExecutionService | 执行报表查询,组装结果集 | ReportAggregate, DataSource |
| ReportValidationService | 校验报表参数合法性、数据源可用性 | ReportAggregate, DataSourceRepository |
| ReportExportService | 将报表结果导出为 PDF/Excel/CSV | ReportAggregate, ExportAdapter |
// ReportExecutionService 是报表执行领域服务
type ReportExecutionService struct {
dataSourceRepo DataSourceRepository
resultBuilder *ReportResultBuilder
}
func NewReportExecutionService(dsRepo DataSourceRepository, builder *ReportResultBuilder) *ReportExecutionService {
return &ReportExecutionService{
dataSourceRepo: dsRepo,
resultBuilder: builder,
}
}
// Execute 执行报表查询,返回结果集
func (s *ReportExecutionService) Execute(ctx context.Context, agg *ReportAggregate) (*ReportResult, error) {
// 1. 获取数据源
ds, err := s.dataSourceRepo.FindByID(ctx, agg.report.DataSourceID)
if err != nil {
return nil, fmt.Errorf("data source not found: %w", err)
}
// 2. 构建查询
query := buildQuery(agg.query, agg.columns)
// 3. 执行查询并组装结果
rawData, err := ds.Query(ctx, query)
if err != nil {
return nil, fmt.Errorf("query execution failed: %w", err)
}
return s.resultBuilder.Build(rawData, agg.style), nil
}
领域服务 vs 应用服务: 领域服务包含业务规则(如“报表执行前必须校验数据源状态”),而应用服务只负责编排领域服务、事务管理和权限校验。保持领域服务纯净,不引入 HTTP、gRPC 等基础设施概念。
2.5 模型设计总结
报表领域模型的分层设计遵循 DDD 战术模式,各元素职责清晰:
- 实体: ReportDefinition 拥有唯一标识,状态可变,是业务操作的主体。
- 值对象: QueryParam、ReportStyle、ColumnDef 不可变,通过值相等判断,降低系统复杂度。
- 聚合根: ReportAggregate 作为边界,封装实体与值对象的业务一致性,外部只能通过聚合根方法操作。
- 领域服务: 处理跨聚合根或依赖外部资源的业务逻辑,保持无状态,通过接口与基础设施解耦。
在 Go 实现中,建议将每个领域元素放在独立的 package 内(如 entity/、valueobject/、aggregate/、service/),通过接口定义依赖关系,确保领域层的独立性和可测试性。