如何记录流程中的步骤状态?

时间:2014-01-09 17:03:35

标签: java spring hibernate

我有一个Web应用程序,它从用户那里获取输入并使用它根据调用各种外部Web服务的结果生成报告。

我想跟踪报告生成的进度,能够查看每个步骤的状态,开始时间和停止时间。

我添加了域对象JobJobStep

@Entity
@Table(name="jobs")
@Data
@EqualsAndHashCode(callSuper=false, of={ "id" })
@ToString()
public class Job extends DomainObject {
  @NotNull
  @OneToMany(cascade=CascadeType.ALL)
  @JoinColumn(name="job_id")
  private Set<JobStep> steps = new TreeSet<JobStep>();

  protected Job() {/*Hibernate requirement*/}

  public Job() {
    // Create all the steps in the beginning with the default settings:
    // status=waiting, date_time both null.
    for (JobStep.Type stepType : JobStep.Type.values()) {
      JobStep step = new JobStep(stepType);
      steps.add(step);
    }
  }

  public Set<JobStep> getSteps() {
    return steps;
  }

  public void startStep(JobStep.Type stepType)
  {
    for (JobStep step : steps) {
      if (step.getType() == stepType) {
        step.start();
        return;
      }
    }
  }

  public void stopStep(JobStep.Type stepType, JobStep.Status status) {
    for (JobStep step : steps) {
      if (step.getType() == stepType) {
        step.stop(status);
        return;
      }
    }
  }
}

@Entity
@Table(name="job_steps")
@Data
@EqualsAndHashCode(callSuper=false, of={ "type", "job" })
@ToString
public class JobStep extends DomainObject implements Comparable<JobStep> {
  private static final Logger LOG = LoggerFactory.getLogger(JobStep.class);

  public enum Type {
    TEST_STEP1,
    TEST_STEP2,
    TEST_STEP3
  }

  public enum Status {
    WAITING,
    RUNNING,
    FINISHED,
    ERROR
  }

  @NotNull
  @Getter
  @Enumerated(EnumType.STRING)
  private Type type;

  @NotNull
  @Setter(AccessLevel.NONE)
  @Enumerated(EnumType.STRING)
  private Status status = Status.WAITING;

  @Setter(AccessLevel.NONE)
  private DateTime start = null;

  @Setter(AccessLevel.NONE)
  private DateTime stop = null;

  @ManyToOne
  private Job job;

  protected JobStep() {/*Hibernate requirement */}

  public JobStep(Type type) {
    this.type = type;
  }

  public void start() {
    assert(status == Status.WAITING);

    status = Status.RUNNING;
    start = new DateTime();
  }

  public void stop(Status newStatus) {
    assert(newStatus == Status.FINISHED ||
           newStatus == Status.ERROR);
    assert(status == Status.RUNNING);

    status = newStatus;
    stop = new DateTime();
  }

  @Override
  public int compareTo(final JobStep o) {
    return getType().compareTo(o.getType());
  }
}

使用JobService类来操纵它们:

@Service
public class JobService {
  private static final Logger LOG = LoggerFactory.getLogger(JobService.class);

  @Autowired
  private JobDAO jobDao;

  @Transactional
  public void createJob() {
    Job job = new Job();
    Long id = jobDao.create(job);

    LOG.info("Created job: {}", id);
  }

  @Transactional
  public Job getJob(Long id) {
    return jobDao.get(id);
  }

  @Transactional
  public void startJobStep(Job job, JobStep.Type stepType) {
    LOG.debug("Starting JobStep '{}' for Job {}", stepType, job.getId());

    job.startStep(stepType);
  }

  @Transactional
  public void stopJobStep(Job job, JobStep.Type stepType,
                          JobStep.Status status) {
    LOG.debug("Stopping JobStep '{}' for Job {} with status {}", stepType,
              job.getId(), status);

    job.stopStep(stepType, status);
  }
}

所以在开始一个步骤的方法中,我可以写:

class Foo() {

  @Autowired
  JobService jobService;        

  public void methodThatStartsAStep(Job job) {
    jobService.startJobStep(job, JobStep.Type.TEST_STEP1);

    // Implementation here
  }
}

我遇到的问题是找到一种方法将Job实例提供给需要它的方法,以便记录步骤已经开始。

显而易见的解决方案是将Job作为参数传递(如上所述),但传递Job并不总是有意义 - 只记录步骤(下面的极端示例) ):

public int multiplySomeNumbers(Job job, int num1, int num2) {
  jobService.startJobStep(job, JobStep.Type.TEST_STEP1);

  // Implementation here.
}

我对理想的解决方案有两点想法:

  1. 使用方面和注释可能导致作业步骤状态发生变化的功能。这使它更少耦合,但方面仍然需要从某个地方获得工作;
  2. Job对象或id存储在类似全局的范围(例如会话或上下文)中。我尝试在我的@Scope("session")上使用JobService,目的是在那里存储Job个实例,但我不断获得java.lang.IllegalStateException: No thread-bound request found。我甚至不确定这是否是这种解决方案的正确用例。
  3. 我的问题是:

    1. 是否可以在某处存储Job或其ID,以便我不必将Job作为参数添加到方法中?
    2. 有没有办法做到这一点,我不知道?

2 个答案:

答案 0 :(得分:0)

您可以将它保存在本地线程中,您可以直接从本地线程访问Object,或者您可以创建自定义Spring范围以获取有关自定义范围http://springindepth.com/book/in-depth-ioc-scope.html的更多信息。您可以在自定义范围中定义作业并将其注入到bean中。


编辑:只有当您的整个流程在单线程中运行并且您的作业步骤是静态的时,您才能按照您提到的流程进行操作。如果您的工作不是静态的(意味着调用外部服务/外部服务的顺序可能会根据输入进行更改),我将实现链责任和命令模式(命令作为实际流程)和链作为您的工作步骤。然后,您可以根据配置跟踪/停止/更改步骤。

答案 1 :(得分:0)

问:问题2,我将走出困境,尽可能广泛地定义这个问题。

您似乎正在重新实现Spring Batch。 Batch广泛支持定义和执行作业,持续工作进度和支持恢复。它还具有记住状态和将状态从一个步骤移动到另一个步骤的背景,面向块的处理,以及通常经过深思熟虑和广泛的基础设施,包括一堆用于常见工作流的读者和编写者。

随意忽略这个答案,我只是想把这个建议扔到那里,以免它让你无所事事。