Post

(Spring Batch) JobParameter 관리

JobParameter 불러오기

1
2
3
4
5
6
7
8
9
@JobScope @Bean
fun jobListener(
    // 방법 1. @Value 애너테이션으로 불러온다.
    @Value("#{jobParameters[fileName]}") fileName: String?,
): JobExecutionListener {
    return object : JobExecutionListener {
        override fun beforeJob(jobExecution: JobExecution) {
            // 방법2. jobExecution이나 stepExecution 이용해서 불러온다.
            jobExecution.jobParameters.getParameter("fileName")

jobParameter는 런타임에 실체화되는 JobScope, StepScope Bean에서만 불러올 수 있다.
일반 Bean은 애플리케이션 시작 시점에 생성되는데, 이 시점에는 jobParameter 참조가 불가능하다.
(Spring Batch) scope bean의 이해

JobParameter 관리

잡파라미터를 묶어주는 클래스를 어떤 기준으로 묶어서 그룹핑?

요구사항

1
2
3
MainJob (step1, step2, step3)
VerificationJob (step2, step3)
SendJob (step3)

배치는 보통 중간 실패 시 재수행을 고려해서 위와 같이 스텝 진행에 따라 subset 잡들을 구성하는 경우도 있는데 이 때 JobParameter 관리는 어떻게 되어야 하는가?

  • 여러 스텝에 걸쳐 공통으로 쓰는 파라미터도 있고, 특정 스텝에서만 쓰는 파라미터도 있는 상황
  • 어떤 스텝이 A잡에 포함되어 실행 될 때와, B잡에 포함되어 실행될 때 각각 파라미터 초기화가 달라야 하는 상황 (e.g., MainJob 실행 할 때는 시간 지정 불가. 이후 subset Job 실행 할 때는 재수행을 위해 시간 지정 가능)

[!info] silver bullet은 없다.
아래 안이 유효한 것은 어디까지나 이와 비슷한 요구사항을 만족해야 할 때다.
요구사항이 달라지면 최적의 설계도 달라진다.

1안. 잡파라미터 관리 클래스 없고, 잡파라미터 받아서 초기화 하는 부분이 각 step 별로 찢어져 있다면?

  • 단점
    • 같은 의미의 파라미터를 step 별로 따로 정의하게 되는 등 관리가 어려움.
    • Job 실행 할 때 어떤 param들이 필요한지를 알아보려면 step 별로 찾아다니면서 봐야 함. (한 눈에 이 잡이 필요로 하는 파라미터를 파악 하기 어려움)
    • fail-fast가 안됨. 파라미터를 잘못 넘기면 step 일부 진행하다가 중간에 실패.
    • 메서드 파라미터로 바로 받기 때문에IDE Go To 기능 사용 할 수 없음.
    • 유지보수하기 가장 나쁨.

2안. 스텝 별로 JobParameter 관리 클래스를 둔다면?

1
2
3
class Step1Params
class Step2Params
class Step3Params
  • 초기화, 유효성 검증은 JobListener에서 호출.
  • 장점
    • 어떤 잡을 실행 할 때 어떤 잡파라미터가 필요한지 JobListener 참고하면 바로 알 수 있음.
    • 어차피 JobConfig에서 Step 불러와야 하는데, 이 때 StepParam도 불러와서 생성해주는게 유지보수 편하고 자연스러움.
    • 각 Step에서는 자기 자신 StepParam만 보면 되므로 구조가 간단해짐. Step으로 응집되는 구조.
  • 단점
    • 모든 step에서 써야하는 공통 필드가 각 StepParams에 위치하면서 중복이 늘어난다는게 단점.
  • 제일 괜찮아 보이는 방법.

3안. Job 묶음 별로 JobParameter 관리 클래스를 둔다면? (step1 + step2 + step3에서 필요한 파라미터를 관리하는 하나의 클래스)

1
class AJobParams
  • MainJob, SendJob은 같은 JobMetaData를 사용하지만, 그 안에서 어떤 필드가 required이고 어떻게 초기화 되어야 하는지는 MainJob, SendJob이 각자 다를 수 있음.
  • 따라서 JobMetaData 초기화 부분은 Job 마다 독립적인 JobListener에서 수행 해야만 함. (유효성 검증도)
  • 장점
    • step1~3에서 모두 써야 하는 공통 필드를 중복 없이 관리 할 수 있음.
  • 단점
    • MainJob(step1 + step2 + step3)이고 SendJob(step3) 이면, SendJob을 실행 할 때는 일부 필드만 초기화, 나머지 필드는 초기화X 상태가 됨. (항상 모든 job parameter를 필요하지도 않은데 넘기도록 하는 것은 실행 시 너무 불편하기 때문에 이렇게 할 수 밖에 없음.)
  • 나쁘진 않지만 복잡도가 좀 있다.

4안. 각각의 sub Job 별로 JobParameter 클래스를 따로 둔다면?

1
2
3
class MainJobParams
class VerificationJobParams
class SendJobParams
  • 이걸 다 찢어놓으면 각 스텝에서 참조 하기가 곤란하다는게 문제. 모든 JobMeta를 일단 참조하면서 현재 step이 어떤 Job 아래에서 실행되고 있는지를 확인하고 그에 맞는 JobMeta를 가져와 써야 하는데 구조가 어색함.
  • Step에서 JobParam 관리 클래스 참조는 Step 별 interface로 하고, 현재 실행하는 Job에 따라 해당하는 JobParam Bean만 생성할 수 있으면 가능하긴 함. (다른 JobParam Bean은 생성되어서는 안됨)
  • 아무튼 좀 번거롭고 구조가 어색해보임.

2안이 가장 나아보임.

JobParameter 관리 객체, Bean으로? static object로?

  • JobParameter는 런타임에 결정되는 값이라 final 변수에 할당 할 수 없음.
  • JobParameter는 JobScope에 종속적인 값이므로 이를 묶어주는 VO도 JobScope/StepScope Bean으로 관리하는게 타당하나… Scope Bean은 스프링 외부(e.g., DTO)에서 참조가 불가능해 곤란한 경우가 종종 있다.
  • 하지만 원칙적으로는 Scope Bean을 쓰는게 좋아보인다.
  • Delegates.initOnlyOnce 같은 패턴을 이용하면 도움이 될 수도.

JobParameter 유효성 검증

유효성 검증을 JobListener에서 하면 좋은 이유

  • 유효성 검증을 Listener가 아닌 JobParameter 관리 클래스에서 한다면?
    • step1에서는 param1이 필수인데, step3에서는 param1을 사용하지 않는 등 케이스 충돌이 발생함.
  • 각 StepListener에서 한다면?
    • param1이 존재하는지 체크 정도는 StepListener에서도 가능하지만 JobParameter 관리 클래스에서 임의값으로 초기화한건지, user input으로 초기화한건지는 이미 초기화가 끝난 다음이라 구분하기 어렵다.
    • fail-fast 불가능하다.
  • JobListener에서 한다면? 👍
    • 각 Job 마다 필수로 요구하는 파라미터만 선택적으로 유효성 검증 할 수 있음.
    • 파라미터가 정상이 아니면 fail-fast 가능
    • JobListener에서 Programmatic 하게 빈 생성 하면 빈 충돌 막을 수 있음.
    • 따라서 Job을 보고 어떤 파라미터가 필요한지 한눈에 알 수 있음.👍
1
2
3
4
5
@StepScope @Component
class FileDownloadStepParams {
    lateinit var fileName: String,
    ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@JobScope @Bean
fun jobListener(
    @Value("#{jobParameters[fileName]}") fileName: String?,
): JobExecutionListener {
    return object : JobExecutionListener {
        override fun beforeJob(jobExecution: JobExecution) {
            require(!fileName.isNullOrBlank())
            
            fileDownloadStepParams.fileName = fileName
            fileReadAndDbLoadStepParams.fileName = fileName
            fileReadAndDbLoadStepParams.isRepurchase = false
            
            logger.info... 

JobParametersValidator

https://devfunny.tistory.com/779
JobParametersValidator 라는게 있어서 이를 사용하는 것을 권장.

  • 직접 정의해도 되고, DefaultJobParametersValidator를 사용해도 됨.
  • Default~의 경우 required, optional에 대한 유무 체크 정도를 제공하는데, enum 파라미터의 경우 일단 string으로 들어오게 되면 validator에서 걸리지는 않지만 해당 enum으로 mapping 할 때 에러 발생함.
This post is licensed under CC BY 4.0 by the author.