
최근에 대규모 리팩토링을 몇번 거치고나니 이후 개발 속도가 현저히 빨라졌다. 기본 토대가 중요함을 뼈저리 느끼고, 사람들이 얘기하는 좋은 코드를 추구하면 생기는 이점이 체감되는 이 기분. 절겁다.. 그래서 그런지 요새 바람은 나보다 경력이 있고 실력이 있는 멘토가 있었으면 하는 것이다. 빨리 복무 끝내고 동아리라도 들어가고 싶어서 몸이 근질근질
프로젝트 내에 스케줄링을 통한 로직을 몇개 구현해놓으면 좋을 것 같다는 얘기가 나왔다.
- Role_Admin을 생성하여 Remove Member logic을 바로 지우는 것보다 "휴면 상태"를 추가하여 어플리케이션에서 자동삭제
- media 파일과 url에 관한 문제 해결
이번 글은 scheduled annotation을 적용하기 위한 세팅과 1번 문제를 해결하고자 한다.
Scheduled annotation을 사용함에 다음을 주의해야한다.
일정 주기의 반복적인 로직을 사용자가 아닌 어플리케이션에서 자동 실행한다.
[Application.class]
@EnableScheduling
@EnableJpaAuditing
@SpringBootApplication
public class WaggleApplication {
public static void main(String[] args) {
SpringApplication.run(WaggleApplication.class, args);
}
//중략
}
어플리케이션 클래스 위에 @EnableScheduling 어노테이션 추가해준다. 이러면 어플리케이션이 @Scheduled 어노테이션이 붙어있는 로직을 전역으로 확인하고 실행시켜준다.
@Scheduled(cron = "0 0 0 * * ?")
@Override
public void deleteDormantMember() {
List<Member> memberList = memberRepository.findByRole(Role.DORMANT);
LocalDateTime today = LocalDateTime.of(LocalDate.now(), LocalTime.MIDNIGHT);
memberList.stream()
.filter(member -> member.getLastModifiedDate().isBefore(today.minusDays(1)))
.forEach(member -> deleteMember(member.getId()));
}
그리고 자동 실행하고자 하는 로직에 어노테이션을 붙이고 주기를 설정해주면 이제 어플리케이션에서 주기에 맞게 로직을 자동실행해준다. 별다른 api 호출도 필요 없다!
위에 Scheduled 어노테이션 외에도 확인 되는 것이 cron = "~" 부분이다. 이 부분은 일정한 주기를 개발자가 설정해주는 것이다. 아래는 몇가지 세팅할 수 있는 옵션에 대한 것이다.
cron = "0 0 0 * * ?", timeZone = "Asia/Seoul"
-> "초 분 시 일 월 요일"
여기서 * 는 all, ?는 특정 조건을 제한하지 않겠다는 의미이다. 따라서 위 조건은 모든 날짜의 자정 정각에 실행하겠다는 얘기이다. timeZone은 시간 설정 지역을 의미한다.
fixedDelay = 1000L
-> 이전 작업 종료 1초 후
fixedRate = 1000L
-> 고정 시간 간격(위 예시는 1초 간격으로 실행)
-> 이전 작업이 완료될 때까지 다음 작업이 진행되지 않음
이제 프로젝트의 적용을 보여주고자 한다.
상황설명은 위에서 얘기했던 것 처럼 member 엔티티를 삭제할 때이다. 어떤 어플리케이션이든 회원관련 정보는 다른 정보들과 연관관계를 복잡하게 맺고 있을 경우가 많다. 우리 프로젝트 또한 마찬가지이다. 그리고 일반적인 서비스에서 회원 정보를 한번에 삭제하는 경우가 많지 않다. 주로 휴면 -> 자동 삭제 와 같은 단계를 거치는 것을 여러 어플리케이션에서 확인할 수 있다.
따라서 먼저 Dormant(휴면) 역할을 만들어주고 회원이 어플리케이션에서 정보를 삭제하고자 하면 역할 전환(user->dormant)api를 호출하도록 해준다. 그리고 스케줄링 시각이 Dormant 회원의 마지막 수정시각 보다 7일 이후인 member 엔티티들을 삭제시켜주도록 한다. (간단히 얘기하면 휴면 전환 이후 삭제 스케줄링 시기에 7일 초과면 삭제가 되는 것이다.)
체크는 매일 자정. 테스트를 위해 지금은 7일이 아닌 1일이 지났을 때마다 지우도록 해줬다.
MemberServiceImpl.class
@Override
public Long convertRole(Member member, Role to) {
member.changeRole(to);
return member.getId();
}
@Scheduled(cron = "0 0 0 * * ?")
@Override
public void deleteDormantMember() {
List<Member> memberList = memberRepository.findByRole(Role.DORMANT);
LocalDateTime today = LocalDateTime.of(LocalDate.now(), LocalTime.MIDNIGHT);
memberList.stream()
.filter(member -> member.getLastModifiedDate().isBefore(today.minusDays(1)))
.forEach(member -> deleteMember(member.getId()));
}
@Override
public void deleteMemberAsAdmin(Member member, Long memberId) {
if (!member.getRole().equals(Role.ADMIN)) {
throw new MemberHandler(ErrorStatus.MEMBER_REQUEST_IS_UNACCEPTABLE_BECAUSE_OF_AUTHORIZATION);
}
deleteMember(memberId);
}
MemberApiController.class
@Operation(summary = "🔑 휴면계정 등록", description = "특정 회원을 휴면계정으로 전환합니다. 하루동안 휴면계정을 풀지 않으면 회원관련 정보가 모두 삭제됩니다")
@ApiErrorCodeExample({
ErrorStatus._INTERNAL_SERVER_ERROR
})
@PutMapping("/role/dormant")
public ApiResponseDto<Boolean> convertDormant(@AuthUser Member member) {
memberCommandService.convertRole(member, Role.DORMANT);
return ApiResponseDto.onSuccess(Boolean.TRUE);
}
@Operation(summary = "🔑 휴면계정 해제", description = "특정 회원을 휴면계정에서 일반회원으로 전환합니다.")
@ApiErrorCodeExample({
ErrorStatus._INTERNAL_SERVER_ERROR
})
@PutMapping("/role/user")
public ApiResponseDto<Boolean> convertUser(@AuthUser Member member) {
memberCommandService.convertRole(member, Role.USER);
return ApiResponseDto.onSuccess(Boolean.TRUE);
}
@Operation(summary = "🔑 회원 강제 삭제", description = "특정 회원을 관리자가 강제 삭제합니다.")
@ApiErrorCodeExample({
ErrorStatus._INTERNAL_SERVER_ERROR
})
@DeleteMapping("/{memberId}/force")
public ApiResponseDto<Boolean> deleteMemberForce(@AuthUser Member admin, @PathVariable Long memberId) {
memberCommandService.deleteMemberAsAdmin(admin, memberId);
return ApiResponseDto.onSuccess(Boolean.TRUE);
}
admin 역할도 추가해줘서 관리자가 데이터를 처리할 때는 딜레이 없게끔 해준다.

스케줄링이 제대로 돌아가는지를 확인할 때는 fixedRate = 3000L(30초)로 설정한 뒤 로그를 찍어본 결과이다.

이것은 실제로 로컬에서 dormant 계정을 삭제해주도록 한 것이다. 단지 멤버 조회만 되는 것으로 확인이 되었는데 이유는 휴면 이후 하루 초과가 기준이었기 때문이다. 다행히 조건에 부합하지 않으면 삭제가 되지 않는 것도 우연찮게 확인이 되었다.
이후 기준을 변경해주어서 다시 시도했을 때는 제대로 삭제가 되었다.
다음 글에는 스케줄링을 통한 미디어 파일 처리에 대해서 다뤄볼 예정이다. 미디어 파일 처리는 생각보다 레퍼런스가 많이 없었기 때문에 처음 설계가 굉장히 복잡하고, 복잡하고, 또 복잡했던... 하지만 스케줄링을 통해 정말 간단하게 변경할 수 있었다.