본문 바로가기
카테고리 없음

문제 해결 경험: 대출 기한 초과 시 신용 점수 감점 기능

by codeomni 2025. 4. 8.
반응형

📌 배경

아이의 금융 습관 교육을 위한 대출 기능을 제공하면서, 기한 내에 상환을 하지 않으면 신용 점수가 감점되도록 하는 기능이 필요했습니다. 구체적으로는, 기한이 지난 대출에 대해 매달 신용 점수를 10점씩 감점하고, 이 점수는 0점 이하로 내려가지 않아야 했습니다.

이 기능을 구현하는 과정에서 다음과 같은 고민이 있었습니다:

  • 어떻게 정기적으로 자동 실행되는 감점 로직을 구성할 수 있을까?
  • 감점 대상이 중복으로 감점되지 않도록 하려면 어떤 기준이 필요할까?
  • 실행 도중 문제가 발생해도 트랜잭션과 로그를 통해 복구하거나 추적할 수 있어야 하지 않을까?

🔍 고려한 기술: @Scheduled vs Quartz vs Linux cron

항목 리눅스 cron Spring @Scheduled Spring Quartz
설정 위치 OS 수준 (서버 crontab) 코드 내부 코드 + DB 관리 가능
트랜잭션 처리 수동 구현 필요 Spring 지원 Spring 지원
유지보수 서버마다 수동 관리 버전 관리 가능 Job 동적 생성 가능
장애 복구 불가 일부 가능 HA 구성 가능
동적 변경 불가 불가 가능
적합도 ⭕ (간단한 작업) ✅ (복잡한 작업)

✅ 선택한 기술: Spring @Scheduled

👉 왜 이 기술을 선택했는가?

  • 해당 작업은 매달 1회 고정 주기로 실행되는 단순한 로직으로 Quartz와 같은 무거운 프레임워크보다는 @Scheduled가 더 적합하다고 판단했습니다.
  • Spring 내부에서 JPA를 통한 DB 접근, 트랜잭션 관리, 로깅, 예외 처리 등을 유기적으로 처리할 수 있어 개발 생산성과 유지보수 측면에서 유리했습니다.
  • 테스트 기간에는 2분마다 실행되는 스케줄러를 구성하여 실시간으로 감점 로직을 검증할 수 있었고, 실제 배포 시에는 매일 자정으로 주기를 변경했습니다.

🔧 구현 내용

1️⃣ Loan 엔티티에 lastPenalizedAt 필드 추가

@Column(name = "last_penalized_at")
private LocalDateTime lastPenalizedAt;
  • 최근 감점 시점을 저장하여, 중복 감점 방지를 위한 기준으로 활용하였습니다.

2️⃣ 감점 스케줄러 구현

@Scheduled(cron = "0 0 0 * * ?") // 매일 자정 실행
@Transactional
public void applyMonthlyOverduePenalties() {
    List<Loan> overdueLoans = loanRepository.findAll().stream()
        .filter(loan -> loan.getStatus() == APPROVED)
        .filter(loan -> loan.getDueDate().isBefore(LocalDateTime.now()))
        .filter(loan -> loan.getLastPenalizedAt() == null || loan.getLastPenalizedAt().plusMonths(1).isBefore(LocalDateTime.now()))
        .toList();

    for (Loan loan : overdueLoans) {
        CreditScore score = creditScoreRepository.findByUser(loan.getParentChild().getChild()).orElse(null);
        if (score == null || score.getScore() <= 0) continue;

        int updated = Math.max(0, score.getScore() - 10);
        score.setScore(updated);
        loan.setLastPenalizedAt(LocalDateTime.now());
        log.info("대출 ID {}: 감점 적용 → {}", loan.getLoanId(), updated);
    }
}

🧪 테스트 및 검증

해당 기능은 개발 초기 단계에서 주기를 2분으로 설정하여 테스트했습니다. 테스트 환경에서는 dueDate가 지난 대출 데이터를 수동으로 설정하고, 감점이 실제로 적용되는지 로그를 통해 실시간으로 확인했습니다. 또한 lastPenalizedAt 필드를 기준으로 중복 감점이 발생하지 않는지 검증했습니다.

특히 신용 점수가 0점 이하로 내려가지 않도록 Math.max(0, ...)로 안전하게 처리하였고, 점수 변경 로직에 트랜잭션을 적용하여 실행 중 예외가 발생하더라도 전체 작업이 롤백되도록 구성했습니다. 로그는 Slf4j를 통해 남겨두어, 감점 이력 추적이 가능하게 했습니다.

테스트 결과, 점수는 정확히 10점씩 감소했으며, 한 달 내 중복 감점은 발생하지 않았고, 트랜잭션과 로깅도 정상적으로 작동함을 확인했습니다.

💡 회고 및 느낀 점

이 기능을 구현하면서 단순한 작업일지라도 상태 관리(lastPenalizedAt)와 트랜잭션 처리가 얼마나 중요한지 깨달았습니다.
또한 Spring의 @Scheduled는 간단한 주기 작업을 빠르게 구현할 수 있는 반면, 동적 변경이나 클러스터링에는 한계가 있다는 점도 함께 인식하게 되었습니다.

기능의 복잡도가 커지거나 동적으로 스케줄을 변경해야 하는 요구사항이 생긴다면, Quartz나 Redis 기반 분산 스케줄러로 전환할 수 있도록 구조를 유연하게 설계하는 것이 중요하다는 점도 배웠습니다.

📌 결론

단순하고 고정된 주기 작업에는 @Scheduled가 최적의 선택이었으며,
향후 확장성을 고려할 수 있도록 Quartz 도입 가능성을 염두에 두고 설계했습니다.

댓글