rwadurian/backend/services/reporting-service/docs/ARCHITECTURE.md

18 KiB

架构设计文档

1. 概述

Reporting Service 采用 领域驱动设计 (DDD) 结合 六边形架构 (Hexagonal Architecture) 模式构建,实现业务逻辑与技术实现的解耦。

2. 架构图

┌─────────────────────────────────────────────────────────────────────────────┐
│                              API Layer (端口)                                │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────────────────┐  │
│  │ ReportController│  │ ExportController│  │     HealthController        │  │
│  └────────┬────────┘  └────────┬────────┘  └─────────────────────────────┘  │
│           │                    │                                             │
│           ▼                    ▼                                             │
│  ┌─────────────────────────────────────────────────────────────────────────┐│
│  │                       Application Layer                                  ││
│  │  ┌──────────────────┐  ┌──────────────────┐  ┌────────────────────────┐ ││
│  │  │ Commands/Queries │  │   Schedulers     │  │  Application Services  │ ││
│  │  │ - GenerateReport │  │ - CronJobs       │  │ - ReportingAppService  │ ││
│  │  │ - ExportReport   │  │ - Periodic Tasks │  │                        │ ││
│  │  └────────┬─────────┘  └──────────────────┘  └────────────────────────┘ ││
│  └───────────┼──────────────────────────────────────────────────────────────┘│
│              │                                                               │
│              ▼                                                               │
│  ┌─────────────────────────────────────────────────────────────────────────┐│
│  │                          Domain Layer (核心)                             ││
│  │  ┌──────────────────┐  ┌──────────────────┐  ┌────────────────────────┐ ││
│  │  │    Aggregates    │  │     Entities     │  │    Value Objects       │ ││
│  │  │ - ReportDefinit. │  │ - ReportFile     │  │ - DateRange            │ ││
│  │  │ - ReportSnapshot │  │ - AnalyticsMetric│  │ - ReportPeriod         │ ││
│  │  └──────────────────┘  └──────────────────┘  │ - SnapshotData         │ ││
│  │                                              └────────────────────────┘ ││
│  │  ┌──────────────────┐  ┌──────────────────┐  ┌────────────────────────┐ ││
│  │  │  Domain Events   │  │ Domain Services  │  │   Repository Ports     │ ││
│  │  │ - SnapshotCreated│  │ - ReportGenSvc   │  │ (Interfaces Only)      │ ││
│  │  │ - ReportExported │  │                  │  │                        │ ││
│  │  └──────────────────┘  └──────────────────┘  └────────────────────────┘ ││
│  └─────────────────────────────────────────────────────────────────────────┘│
│              ▲                                                               │
│              │                                                               │
│  ┌─────────────────────────────────────────────────────────────────────────┐│
│  │                     Infrastructure Layer (适配器)                        ││
│  │  ┌──────────────────┐  ┌──────────────────┐  ┌────────────────────────┐ ││
│  │  │   Persistence    │  │  External APIs   │  │    Export Services     │ ││
│  │  │ - Prisma/PG      │  │ - LeaderboardSvc │  │ - ExcelExport          │ ││
│  │  │ - Repositories   │  │ - PlantingSvc    │  │ - CSVExport            │ ││
│  │  │ - Mappers        │  │                  │  │ - PDFExport            │ ││
│  │  └──────────────────┘  └──────────────────┘  └────────────────────────┘ ││
│  │  ┌──────────────────┐  ┌──────────────────┐                             ││
│  │  │   Redis Cache    │  │      Kafka       │                             ││
│  │  │ - ReportCache    │  │ - EventPublisher │                             ││
│  │  └──────────────────┘  └──────────────────┘                             ││
│  └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘

3. 目录结构

src/
├── api/                          # API层 - 入站端口
│   ├── controllers/              # HTTP控制器
│   │   ├── report.controller.ts  # 报表API
│   │   ├── export.controller.ts  # 导出API
│   │   └── health.controller.ts  # 健康检查
│   └── dto/                      # 数据传输对象
│       ├── request/              # 请求DTO
│       └── response/             # 响应DTO
│
├── application/                  # 应用层 - 用例编排
│   ├── commands/                 # 命令处理器
│   │   ├── generate-report/      # 生成报表命令
│   │   └── export-report/        # 导出报表命令
│   ├── queries/                  # 查询处理器
│   │   └── get-report-snapshot/  # 获取快照查询
│   ├── schedulers/               # 定时任务
│   │   └── report-generation.scheduler.ts
│   └── services/                 # 应用服务
│       └── reporting-application.service.ts
│
├── domain/                       # 领域层 - 业务核心
│   ├── aggregates/               # 聚合根
│   │   ├── report-definition/    # 报表定义聚合
│   │   └── report-snapshot/      # 报表快照聚合
│   ├── entities/                 # 实体
│   │   ├── report-file.entity.ts
│   │   └── analytics-metric.entity.ts
│   ├── value-objects/            # 值对象
│   │   ├── date-range.vo.ts
│   │   ├── report-period.enum.ts
│   │   ├── report-type.enum.ts
│   │   └── snapshot-data.vo.ts
│   ├── events/                   # 领域事件
│   │   ├── snapshot-created.event.ts
│   │   └── report-exported.event.ts
│   ├── repositories/             # 仓储接口
│   │   ├── report-definition.repository.interface.ts
│   │   └── report-snapshot.repository.interface.ts
│   └── services/                 # 领域服务
│       └── report-generation.service.ts
│
├── infrastructure/               # 基础设施层 - 出站适配器
│   ├── persistence/              # 持久化
│   │   ├── prisma/               # Prisma配置
│   │   ├── repositories/         # 仓储实现
│   │   └── mappers/              # 对象映射器
│   ├── external/                 # 外部服务客户端
│   │   ├── leaderboard-service/
│   │   └── planting-service/
│   ├── export/                   # 导出服务实现
│   │   ├── excel-export.service.ts
│   │   ├── csv-export.service.ts
│   │   └── pdf-export.service.ts
│   └── redis/                    # Redis缓存
│       └── report-cache.service.ts
│
├── shared/                       # 共享模块
│   ├── decorators/               # 自定义装饰器
│   ├── filters/                  # 异常过滤器
│   ├── guards/                   # 守卫
│   ├── interceptors/             # 拦截器
│   └── strategies/               # Passport策略
│
└── config/                       # 配置
    ├── app.config.ts
    ├── database.config.ts
    └── redis.config.ts

4. 核心设计原则

4.1 依赖倒置原则 (DIP)

// 领域层定义接口 (端口)
export interface IReportSnapshotRepository {
  save(snapshot: ReportSnapshot): Promise<ReportSnapshot>;
  findById(id: bigint): Promise<ReportSnapshot | null>;
  findByCodeAndPeriodKey(code: string, periodKey: string): Promise<ReportSnapshot | null>;
}

// 基础设施层实现接口 (适配器)
@Injectable()
export class ReportSnapshotRepository implements IReportSnapshotRepository {
  constructor(private readonly prisma: PrismaService) {}

  async save(snapshot: ReportSnapshot): Promise<ReportSnapshot> {
    // Prisma实现细节
  }
}

4.2 聚合根设计

// ReportSnapshot 聚合根
export class ReportSnapshot extends AggregateRoot {
  private readonly _id: bigint;
  private _reportType: ReportType;
  private _reportCode: string;
  private _reportPeriod: ReportPeriod;
  private _snapshotData: SnapshotData;
  private _events: DomainEvent[] = [];

  // 工厂方法 - 创建新快照
  public static create(props: CreateSnapshotProps): ReportSnapshot {
    const snapshot = new ReportSnapshot(props);
    snapshot.addDomainEvent(new SnapshotCreatedEvent(snapshot));
    return snapshot;
  }

  // 从持久化重建 - 不触发事件
  public static reconstitute(props: ReconstitutionProps): ReportSnapshot {
    return new ReportSnapshot(props);
  }

  // 业务行为
  public updateData(newData: SnapshotData): void {
    this.validateDataUpdate(newData);
    this._snapshotData = newData;
    this._updatedAt = new Date();
  }
}

4.3 值对象不可变性

// DateRange 值对象
export class DateRange {
  private readonly _startDate: Date;
  private readonly _endDate: Date;

  private constructor(startDate: Date, endDate: Date) {
    this._startDate = startDate;
    this._endDate = endDate;
    Object.freeze(this);
  }

  public static create(startDate: Date, endDate: Date): DateRange {
    if (startDate > endDate) {
      throw new DomainException('Start date must be before end date');
    }
    return new DateRange(startDate, endDate);
  }

  public equals(other: DateRange): boolean {
    return this._startDate.getTime() === other._startDate.getTime() &&
           this._endDate.getTime() === other._endDate.getTime();
  }
}

5. 数据流

5.1 报表生成流程

┌─────────┐    ┌──────────────┐    ┌─────────────────┐    ┌──────────────┐
│ Client  │───▶│ ReportCtrl   │───▶│ GenerateHandler │───▶│ DomainSvc    │
└─────────┘    └──────────────┘    └─────────────────┘    └──────┬───────┘
                                                                  │
                    ┌─────────────────────────────────────────────┘
                    ▼
┌──────────────────────┐    ┌──────────────────┐    ┌──────────────────┐
│ External Services    │───▶│ ReportSnapshot   │───▶│ Repository       │
│ (Leaderboard/Plant.) │    │ (Aggregate)      │    │ (Save to DB)     │
└──────────────────────┘    └──────────────────┘    └──────────────────┘

5.2 报表导出流程

┌─────────┐    ┌──────────────┐    ┌─────────────────┐
│ Client  │───▶│ ExportCtrl   │───▶│ ExportHandler   │
└─────────┘    └──────────────┘    └────────┬────────┘
                                             │
         ┌───────────────────────────────────┴───────────────────────────┐
         ▼                           ▼                          ▼        ▼
┌──────────────────┐    ┌──────────────────┐    ┌──────────────────┐
│ ExcelExportSvc   │    │ CSVExportSvc     │    │ PDFExportSvc     │
│ (ExcelJS)        │    │ (csv-stringify)  │    │ (PDFKit)         │
└──────────────────┘    └──────────────────┘    └──────────────────┘
         │                       │                        │
         └───────────────────────┴────────────────────────┘
                                 ▼
                    ┌──────────────────────┐
                    │ ReportFile Entity    │
                    │ (Save to Storage)    │
                    └──────────────────────┘

6. 关键组件

6.1 报表类型 (ReportType)

类型 描述 数据来源
LEADERBOARD_REPORT 排行榜报表 Leaderboard Service
PLANTING_REPORT 种植报表 Planting Service
COMMUNITY_REPORT 社区报表 Community Service
SYSTEM_ACCOUNT_REPORT 系统账户报表 Account Service
ANALYTICS_DASHBOARD 分析仪表板 多数据源聚合

6.2 报表周期 (ReportPeriod)

周期 描述 用途
DAILY 日报 每日运营数据
WEEKLY 周报 周度趋势分析
MONTHLY 月报 月度业绩汇总
QUARTERLY 季报 季度财务报表
YEARLY 年报 年度总结
CUSTOM 自定义 灵活时间范围

6.3 导出格式 (OutputFormat)

格式 实现库 特点
EXCEL ExcelJS 支持样式、图表、多Sheet
CSV csv-stringify 轻量、通用
PDF PDFKit 适合打印、分发
JSON 内置 API集成、数据交换

7. 扩展点

7.1 添加新报表类型

  1. ReportType 枚举中添加新类型
  2. 创建对应的外部服务客户端 (如需要)
  3. GenerateReportHandler 中添加数据获取逻辑
  4. 更新 ReportDefinition 种子数据

7.2 添加新导出格式

  1. OutputFormat 枚举中添加新格式
  2. 创建新的导出服务 (实现 IExportService 接口)
  3. ExportReportHandler 中注册新服务

7.3 集成新数据源

  1. infrastructure/external/ 下创建服务客户端
  2. 定义数据传输接口
  3. 在应用层注入并使用

8. 安全考虑

  • 认证: JWT Bearer Token (可配置)
  • 授权: 基于角色的访问控制 (RBAC)
  • 数据脱敏: 敏感数据在导出时进行脱敏处理
  • 审计日志: 所有报表操作记录到审计表