모종닷컴

Quartz Job Scheduling 2편 - API로 활용 본문

Programming

Quartz Job Scheduling 2편 - API로 활용

모종 2022. 8. 27. 17:00
반응형

안녕하세요. 오늘은 지난 Quartz Job Schedule 1편 - 소개 및 간단한 예제에 이어서 Quartz를 좀 더 다양하게 활용하기 위한 포스팅을 하려고 합니다. Quartz를 많이 다뤄보지는 못해서 활용도가 조금 떨어질 수 있지만 이런식으로 활용할 수 있겠구나 정도의 가벼운 마음으로 읽어주시면 감사하겠습니다 :) 

이번 예제에서는 Controller를 활용할 예정이라 혹시라도 의존성이 없다면 web 관련 dependencies를 구성해주시기 바랍니다. 제대로 확인은 안했지만 spring-boot-starter-web 정도만 추가하면 되지 않을까 싶습니다.

QuartzApiController 만들기

이번 포스팅에서 예제는 모두 api 형식으로 제공할 예정이라 컨트롤러를 먼저 작성하도록 하겠습니다. 

private val log = KotlinLogging.logger { }

@RestController
@RequestMapping("/api/quartz")
class QuartzController(
    private val scheduler: Scheduler
) {

}

KotlinLogging 라이브러리 없다면 LoggingFactory.getLogger(..)를 이용하셔도 좋습니다. KotlinLogging 사용하길 원한다면 의존성에 io.github.microutils:kotlin-logging 를 추가해주시면 됩니다. KotlinLogging이 뭔지 궁금하다면 https://github.com/MicroUtils/kotlin-logging 페이지를 보시면 될 겁니다. 일반 slf4j 라이브러리의 기능을 모두 지원할 뿐더러 보일러플레이트 코드 제거 및 lazy-evaluate 를 지원합니다. 

스프링에서 지원해주는 spring-boot-starter-quartz 를 이용하면 scheduler가 자동으로 등록된다고 지난 글에 설명하였습니다. 주입이 잘 될 겁니다.

현재 애플리케이션에서 등록된 JobDetail 출력하기

모든 JobDetail 출력

scheduler는 job과 trigger 세부정보들이 등록되어 있습니다. 그렇다면 이 scheduler에 등록된 jobDetail에 대한 내용들을 출력해주는 API를 하나 만들어 보겠습니다. 아래의 코드를 위에서 만든 컨트롤러 안에 넣어주면 됩니다.

@GetMapping("/jobDetails")
fun getAllJobDetails(): ResponseEntity<List<JobDetailInfo>> {
    val jobInfos = scheduler.jobGroupNames.map { groupName ->
        scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName)).map {
            JobDetailInfo(scheduler.getJobDetail(it))
        }
    }.flatten()
    return ResponseEntity.ok(jobInfos)
}

class JobDetailInfo (
    val groupName: String,
    val jobName: String,
    val desc: String
) {
    constructor(jobDetail: JobDetail): this(
        groupName = jobDetail.key.group,
        jobName = jobDetail.key.name,
        desc = jobDetail.description
    )
}

애플리케이션을 띄우고 해당 api를 요청해보면 됩니다. 저는 아래와 같은 데이터를 받았습니다.

이걸 보니 job 마다 그룹을 지정해 줄 수 있나 보네요. 일단 DEFAULT 그룹은 맘에 들지 않으니 위의 job에 그룹이름을 다른걸로 지정해보겠습니다. 그룹 이름은 상수로 사용될 수 있으니 먼저 eum으로 관리하도록 하겠습니다.

enum class QuartzGroup {
    DEFAULT,
    MONITORING
}

 이제 Job에다가 그룹을 설정하도록 하겠습니다. 지난글 중 QuartzConfig 클래스의 testJobDetail 메서드를 아래와 같이 수정하겠습니다.

@Bean
fun testJobDetail(): JobDetailFactoryBean {
    val jobDataMap = JobDataMap()
    jobDataMap["name"] = "mojong"

    return JobDetailFactoryBean().apply {
        setJobClass(TestJob::class.java) // 어떤 Job에 대한 JobDetail 인지 명시
        setDescription("Invoke Test Job") // 설명
        setDurability(true) // 필수인 듯
        setJobDataMap(jobDataMap)
        setGroup(QuartzGroup.MONITORING.name)
    }
}

이 상태에서 다시 API를 요청해보겠습니다. 아래 사진과 같이 그룹이름이 변경되었네요.

그룹명으로 JobDetail 정보를 가져오도록 수정

위의 API의 기능을 확장해보겠습니다. 만약 요청할 때 group 파라미터를 추가해주면 해당 그룹에 대한 jobDetail만 가져오도록 하고, group 파라미터가 없는 경우 이전 기능그대로 전체 jobDetail 정보를 가져오도록 하겠습니다.

@GetMapping("/jobDetails")
fun getAllJobDetails(group: QuartzGroup?): ResponseEntity<List<JobDetailInfo>> {
    val groupNames = if(group == null) scheduler.jobGroupNames else listOf(group.name)

    val jobInfos = groupNames.map { groupName ->
        scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName)).map {
            JobDetailInfo(scheduler.getJobDetail(it))
        }
    }.flatten()
    return ResponseEntity.ok(jobInfos)
}

위와 같이 코드를 수정하고 먼저 group=DEFAULT를 붙여서 요청해보면 빈리스트가 group=MONITORING를 붙여 요청하면 MONITORING 그룹에 해당하는 jobDetail을 받을 수 있게 되었습니다. 그리고 query param을 지우면 모든 jobDetail을 볼 수 있습니다.

원타임 실행

scheduler를 보다보니 triggerJob이라는 메서드가 있는 것을 봤는데요. 느낌이 강제로 실행을 하는듯합니다. 이걸 이용해서 원타임 실행 api를 만들어보도록 할게요. 

@PostMapping("/trigger")
fun triggerOnetime(@RequestBody request: OnetimeTriggerRequest): ResponseEntity<String> {
    val jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(request.group.name))
    val jobKey = jobKeys.find { it.name == request.jobKeyName } ?: return ResponseEntity.ok("NOT_FOUND")

    scheduler.triggerJob(jobKey)
    return ResponseEntity.ok("OK")
}


class OnetimeTriggerRequest(
    val group: QuartzGroup,
    val jobKeyName: String
)

triggerJob을 호출하기 위해서는 jobKey가 필요합니다. 그래서 그룹과 jobKey 이름을 받아야 합니다. 요청시에 이 내용들을 받도록 스펙을 만들었습니다. validation 까지 넣으면 좋을 것 같지만 시간상 붙이지는 않겠습니다. 위의 코드를 Controller에 추가하고 api를 호출해보면 예상대로 원타임 실행이었네요

마치며

Quartz로 할 수 있는게 상당히 많네요.

이 포스팅에 다 쓰려니 글이 너무 길어져서 3편에 이어서 작성해보도록 하겠습니다. 계획으로는 3편에서는 태스크 정보들을 다이나믹하게 조정하는 방법에 대해서 쓰려고 합니다. 

여기까지 읽어주셔서 감사합니다. 다음편에 만나요

반응형