[061][调度模块]领域驱动的任务调度架构设计与分层实践
1. 整体架构蓝图
该模块采用六边形架构思想,将核心领域(调度、日志)与基础设施(数据库、Web)解耦。核心设计决策如下:
- 任务定义与运行时分离:
JobEntity作为持久化配置,ScheduleTaskManager管理内存中的执行实例,通过Task接口统一抽象。 - 事件驱动的日志记录:状态变更事件异步触发日志持久化,避免阻塞调度主流程。
- 规格模式(Specification):动态查询条件组合,避免冗长的 JPQL 拼接。
┌─────────────────────────────────────────────────────────────┐
│ 表示层 (Web) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ JobEndpoint │ │ScheduleEndpoint│ │ JobLogEndpoint │ │
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
│ DTO/VO 转换
┌─────────────────────────▼───────────────────────────────────┐
│ 应用服务层 (Service) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ JobService │ │ScheduleService│ │ JobLogService │ │
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
│ 领域接口
┌─────────────────────────▼───────────────────────────────────┐
│ 领域层 (Domain) │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ interface Task ◄─── implements ──── JobEntity │ │
│ │ interface TaskRepository ◄─── implements ──── │ │
│ │ DatabaseTaskRepository (适配 JPA) │ │
│ │ interface ChangeStatusEventConsumer ◄─── implements │ │
│ │ JobLogEventConsumer (事件监听) │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
│ JPA
┌─────────────────────────▼───────────────────────────────────┐
│ 基础设施层 (Infrastructure) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ JobRepository│ │JobLogRepository│ │ ScheduleTask │ │
│ │ (JPA) │ │ (JPA) │ │ Manager │ │
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────┘
2. 核心领域模型:Task 接口与 JobEntity 实现
技术亮点:接口统一 + 多态持久化
Task 接口定义了调度器所需的所有配置方法,JobEntity 通过实现该接口,既可作为 JPA 实体,又能直接注入调度管理器。
// 调度核心定义的任务契约
public interface Task {
String getTaskCode();
String getClassSimpleName();
String getCron();
boolean isEnabled();
String getDescription();
Map<String, String> getMetadata();
Duration getInitialDelay();
Integer getMaxFailureCount();
Integer getMaxExecutionCount();
Instant getDueDate();
}
// 领域实体同时实现接口与 JPA 注解
@Data
@Entity
@Table(name = "feat_job")
public class JobEntity extends BaseStatusEntity implements Task {
@Column(length = 64, unique = true, updatable = false)
private String taskCode;
@Column(length = 64)
private String classSimpleName;
@Column(length = 64)
private String cron;
@JdbcTypeCode(SqlTypes.JSON) // ✅ 利用 Hibernate 6 的 JSON 映射
@Column(columnDefinition = "json")
private Map<String, String> metadata;
// ... 其他字段
@Override
public boolean isEnabled() {
// 利用父类状态字段实现软删除 + 预留双重语义
return Objects.equals(getDataStatus(), DataStatusEnum.NORMAL)
|| Objects.equals(getDataStatus(), DataStatusEnum.RESERVED);
}
}
设计精要:
- JSON 元数据:
@JdbcTypeCode(SqlTypes.JSON)自动序列化 Map 为 JSON 列,支持灵活扩展而无需 DDL 变更。 - 状态语义:
NORMAL为正常启用,RESERVED为保留(可被特殊逻辑触发),DELETED为逻辑删除,丰富了生命周期管理。
3. 仓库适配层:DatabaseTaskRepository 的防腐作用
技术亮点:适配器模式隔离 ORM 依赖
@Service
@RequiredArgsConstructor
public class DatabaseTaskRepository implements TaskRepository<JobEntity> {
private final JobRepository jobRepository; // Spring Data JPA
@Override
public Optional<JobEntity> findByTaskCode(String taskCode) {
return jobRepository.findByTaskCode(taskCode);
}
@Override
public List<JobEntity> findAll() {
return jobRepository.findAll();
}
}
通过实现 TaskRepository 接口,上层 ScheduleService 完全不知晓 JPA 的存在。若将来迁移至 MyBatis 或 Redis,只需替换该适配类,业务逻辑零修改。
4. 不可变数据传输:Record + Builder 模式
TaskExecutionDetails 使用 @Builder 注解构建不可变对象,聚合任务定义与运行时状态:
@Builder
public record TaskExecutionDetails(
String taskCode, // 任务标识
String classSimpleName, // 执行类
String cron, // 调度表达式
boolean enabled, // 是否启用
String description, // 描述
Map<String, String> metadata, // 扩展属性(不可变视图)
Duration initialDelay,
Integer maxFailureCount,
Integer maxExecutionCount,
Instant dueDate,
// 以下为运行时动态数据
TaskStatusEnum taskStatus,
String lotNo,
int totalCount,
int totalFailureCount,
Instant startTime,
Instant endTime,
String message,
Throwable throwable
) {
}
技术亮点:
- Record 不可变性:天然线程安全,适合多线程环境下的数据传递。
- Builder 模式:支持部分字段构建,结合
MapUtils.unmodifiableMap保证元数据只读。