3. 报表导出领域建模(一):识别核心领域概念

报表导出模块在大多数企业应用中看似简单——接收请求、查询数据、生成文件、返回下载。然而,当数据量达到百万级、格式要求复杂、调度策略多样时,缺乏领域建模的导出模块会迅速退化为难以维护的“大泥球”。本章开始,我们将从领域驱动设计(DDD)的视角,对报表导出进行核心领域概念识别与建模。

3.1 核心领域概念总览

通过事件风暴与业务专家访谈,我们提炼出报表导出领域的四个核心概念:报表模板、数据源、导出格式、调度任务。这四个概念构成了导出模块的“四梁八柱”,它们之间的关系如下表所示:

核心概念 领域职责 关键属性(示例) 与其他概念的关系
报表模板 定义报表的结构、布局、样式与占位符 模板ID、名称、布局JSON、占位符列表、版本号 引用数据源;绑定导出格式;被调度任务使用
数据源 封装数据获取逻辑,提供统一查询接口 数据源ID、类型(SQL/API/文件)、连接配置、查询语句 被报表模板引用;为导出提供原始数据
导出格式 定义输出文件的格式规范与渲染策略 格式ID、类型(PDF/Excel/CSV)、渲染器类名、配置参数 被报表模板绑定;由调度任务触发渲染
调度任务 管理导出请求的生命周期与执行策略 任务ID、触发方式、执行时间、状态、关联模板ID 关联报表模板;触发数据源查询;调用格式渲染
建模要点: 这四个概念在领域模型中均为实体(Entity),拥有唯一标识和生命周期。不要将它们降级为简单的值对象或DTO,否则后续的扩展性(如模板版本管理、多数据源组合、异步调度)将难以落地。

3.2 报表模板:导出模块的“蓝图”

报表模板是导出模块中最具业务价值的领域概念。它不仅仅是“一个文件”,而是包含了布局结构、数据占位符、样式规则、条件显示逻辑等复杂信息的领域模型。一个典型的报表模板包含以下组成部分:

  • 元信息:模板名称、描述、创建人、版本号、生效时间范围。
  • 布局定义:页眉、页脚、主体区域、分栏结构、表格/图表占位。
  • 数据占位符:标记数据注入位置,如 {{sales_total}}、{{table:order_list}}。
  • 样式规则:字体、颜色、边框、对齐方式、条件格式(如负值标红)。
  • 关联数据源:每个占位符绑定一个数据源查询或API调用。
实践建议: 不要将报表模板直接存储为文件路径或二进制大对象。推荐使用模板定义语言(TDL),将模板序列化为结构化JSON或YAML,存入数据库。这样便于版本控制、差异对比和动态修改。

3.3 数据源:统一的数据访问抽象

数据源是连接报表模板与底层存储的桥梁。在领域模型中,数据源不应暴露具体的数据库连接细节或API调用方式,而是提供一个统一的查询契约:

// 数据源领域接口(Python 伪代码)
class DataSource(ABC):
    @abstractmethod
    def query(self, params: dict, context: ExecutionContext) -> DataSet:
        """执行查询,返回统一的数据集"""
        pass

    @abstractmethod
    def validate(self) -> bool:
        """验证数据源配置是否可用"""
        pass

    @property
    @abstractmethod
    def schema(self) -> DataSchema:
        """返回数据源的结构定义(字段名、类型)"""
        pass

数据源的类型通常包括:

数据源类型 适用场景 性能考量
SQL 数据源 关系型数据库报表 分页查询、索引优化、连接池管理
API 数据源 微服务、第三方系统数据 超时控制、缓存策略、限流
文件数据源 CSV/Excel/Parquet 文件导入 流式读取、内存控制
聚合数据源 多数据源合并、ETL 后输出 内存合并、排序、去重

3.4 导出格式:渲染策略的封装

导出格式是报表导出的“输出端”核心概念。不同的格式(PDF、Excel、CSV、HTML)具有截然不同的渲染引擎和性能特征。在领域模型中,导出格式应封装为策略模式:

# 导出格式策略接口
class ExportFormat(ABC):
    @abstractmethod
    def render(self, template: ReportTemplate, data: DataSet) -> bytes:
        """将模板与数据渲染为指定格式的字节流"""
        pass

    @abstractmethod
    def get_file_extension(self) -> str:
        """返回文件扩展名,如 .pdf, .xlsx"""
        pass

    @abstractmethod
    def get_mime_type(self) -> str:
        """返回 MIME 类型"""
        pass
常见陷阱: 不要将导出格式的渲染逻辑直接写在调度任务或控制器中。这会导致“格式爆炸”——每增加一种格式就需要修改多处代码。使用策略模式将格式实现隔离,新增格式只需添加新的策略类。

3.5 调度任务:导出请求的生命周期管理

调度任务是导出模块的“执行引擎”。它负责接收导出请求、协调数据源查询与格式渲染、处理失败重试、记录执行日志。调度任务的状态机是领域建模的关键:

调度任务状态机 待处理 执行中 已完成 失败 重试中 已取消 最大重试次数:3 重试间隔:指数退避 失败原因记录 支持人工干预取消

调度任务的核心属性包括:

  • 任务标识:全局唯一ID,用于追踪和幂等控制。
  • 关联模板:指定使用哪个报表模板进行导出。
  • 执行参数:时间范围、过滤条件、输出路径等。
  • 触发方式:即时触发、定时触发(Cron)、事件触发。
  • 执行记录:开始时间、结束时间、耗时、错误信息、输出文件路径。

3.6 领域概念关系图

将上述四个核心概念整合在一起,形成报表导出模块的领域模型关系图:

报表导出领域模型——核心概念关系 报表模板 布局 · 占位符 · 样式 数据源 SQL · API · 文件 导出格式 PDF · Excel · CSV 调度任务 生命周期 · 重试 · 日志 引用 绑定 关联
建模总结: 报表模板是“蓝图”,数据源是“原料供应”,导出格式是“加工工艺”,调度任务是“生产管理”。四个概念各司其职,通过领域模型中的关联关系协同工作。在后续章节中,我们将基于这四个核心概念,进一步构建聚合、仓储、领域服务等更丰富的领域模型层次。