πŸ“š[Spring] Scheduler Project

텁텁·2025λ…„ 5μ›” 14일

μŠ€ν”„λ§ 첫 ν”„λ‘œμ νŠΈ SchedulerProject κ°€ 끝났닀.
μš”κ΅¬μ‚¬ν•­ 쀑 νŽ˜μ΄μ§€λ„€μ΄μ…˜ κΈ°λŠ₯κ³Ό μœ νš¨μ„± 검사 κΈ°λŠ₯은 끝내 κ΅¬ν˜„ν•˜μ§€ λͺ»ν–ˆλ‹€.
개발 도쀑 λ§‰νžˆλŠ” μˆœκ°„μ΄ κ³„μ†ν•΄μ„œ λ°œμƒν–ˆκ³  κ·Έλ•Œλ§ˆλ‹€ μΆ”κ°€ ν•™μŠ΅κ³Ό μž¬κ°œλ°œμ„ λ°˜λ³΅ν•˜λ‹€ λ³΄λ‹ˆ
λ§ˆκ°μ‹œκ°„μ΄ κ°€κΉŒμ›Œμ‘Œκ³  λͺ¨λ“  μš”κ΅¬μ‚¬ν•­μ„ κ΅¬ν˜„ν•˜κΈ°λŠ” νž˜λ“€λ‹€κ³  νŒλ‹¨ν–ˆλ‹€.
κ·Έλž˜λ„ 첫 μŠ€ν”„λ§ ν”„λ‘œμ νŠΈμΈ 만큼 κ°€λ³κ²Œ νšŒκ³ ν•΄λ³΄κ³  λ§ˆλ¬΄λ¦¬ν•˜κ³ μž ν•œλ‹€.


3-tier Layered Architecture

Controller, Service, Repository λ₯Ό κ±°μΉ˜λŠ” λ°μ΄ν„°μ˜ 흐름과 각각의 μ±…μž„μ— λŒ€ν•΄ 깊이 κ³ λ―Όν•΄ λ³Ό 수 μžˆλŠ” μ‹œκ°„μ΄μ—ˆλ‹€.

//Service Method
@Override
public ScheduleResponseDto findScheduleById(Long id) {
    return scheduleRepository.findScheduleById(id)
            .stream().findAny().orElseThrow(() -> new ScheduleNotFoundException("ν•΄λ‹Ή id와 μΌμΉ˜ν•˜λŠ” 일정이 μ—†μŠ΅λ‹ˆλ‹€."));
}

//Repository Method
@Override
    public List<ScheduleResponseDto> findScheduleById(Long id) {
        return jdbcTemplate.query("select * from schedule where scheduleId=? order by lastModifiedDate desc", scheduleMapper(), id);
    }

Repository λŠ” μ£Όμ–΄μ§„ id둜 쿼리λ₯Ό μ‹€ν–‰ν•˜κ³  κ²°κ³Όλ₯Ό List<ScheduleResponseDto> ν˜•νƒœλ‘œ λ°˜ν™˜ν•œλ‹€.
ν”„λ‘œμ νŠΈλ₯Ό μ‹œμž‘ν•  μ¦ˆμŒμ—” 쿼리 κ²°κ³Όκ°€ λΉ„μ–΄ μžˆμ„ 경우 Repository μ—μ„œ μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œν‚€λŠ” 것이 μžμ—°μŠ€λŸ½λ‹€κ³  μƒκ°ν–ˆλ‹€.
ν•˜μ§€λ§Œ 각 κ³„μΈ΅μ˜ μ±…μž„μ— λŒ€ν•΄ κ³ λ―Όν•˜λ‹€ λ³΄λ‹ˆ μ˜ˆμ™Έ 처리의 μ±…μž„μ€ Service 에 μžˆλ‹€λŠ” 결둠에 λ„λ‹¬ν–ˆλ‹€.

Repository λŠ” μ •μƒμ μœΌλ‘œ DB 에 μ ‘κ·Όν–ˆκ³  κ·Έ 결과둜 빈 리슀트둜 λ°˜ν™˜ν–ˆλ‹€.
μ΄λŠ” λ‹¨μˆœνžˆ ν•΄λ‹Ή 쑰건에 λ§žλŠ” 데이터가 μ—†λ‹€λŠ” 의미일 뿐, Repository λŠ” μžμ‹ μ˜ μ±…μž„μ„ λ‹€ν•œ 것이닀.
그리고 이 κ²°κ³Όλ₯Ό λ°›κ³  λΉ„μ¦ˆλ‹ˆμŠ€ 둜직의 흐름에 따라 μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œν‚¬μ§€ κ·ΈλŒ€λ‘œ μ²˜λ¦¬ν• μ§€λŠ” Service 의 역할인 것이닀.

이처럼 κ³„μΈ΅λ§ˆλ‹€ μ±…μž„μ„ λͺ…ν™•νžˆ λ‚˜λˆ„λŠ” 일은 λ‹¨μˆœν•œ κ΅¬ν˜„λ³΄λ‹€ 훨씬 μ–΄λ ΅κ³  λ§Žμ€ 고민을 ν•„μš”λ‘œ ν•œλ‹€.
ν•˜μ§€λ§Œ 그런 ꡬ쑰적 κ³ λ―Όκ³Ό 반볡이 κ²°κ΅­ 더 λ‚˜μ€ 섀계λ₯Ό λ§Œλ“€μ–΄κ°„λ‹€λŠ” κ±Έ 이번 ν”„λ‘œμ νŠΈλ₯Ό 톡해 직접 λŠλ‚„ 수 μžˆμ—ˆλ‹€.


GlobalExceptionHandler

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler
    public ResponseEntity<ErrorResponse> handleUserNotFoundException(UserNotFoundException ex) {
        ErrorResponse response = new ErrorResponse(ex.getMessage(), HttpStatus.NOT_FOUND.value());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
    }

    @ExceptionHandler
    public ResponseEntity<ErrorResponse> handleScheduleNotFoundException(ScheduleNotFoundException ex) {
        ErrorResponse response = new ErrorResponse(ex.getMessage(), HttpStatus.NOT_FOUND.value());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
    }

    //μΈμ¦μ‹€νŒ¨
    @ExceptionHandler
    public ResponseEntity<ErrorResponse> handlePasswordMismatchException(PasswordMismatchException ex) {
        ErrorResponse response = new ErrorResponse(ex.getMessage(), HttpStatus.UNAUTHORIZED.value());
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
    }

    //λ‘œμ§μƒ μ˜ˆμ™Έκ°€ λ°œμƒν•  일은 μ—†μ§€λ§Œ μ˜ˆμ™Έκ°€ ν„°μ§„κ²½μš° μ„œλ²„λ‚˜ DB의 문제일 ν™•λ₯ μ΄ 있음 500μ—λŸ¬ 리턴
    @ExceptionHandler
    public ResponseEntity<ErrorResponse> handleQueryFailedException(QueryFailedException ex) {
        ErrorResponse response = new ErrorResponse(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR.value());
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
    }
}
@Getter
public class ErrorResponse {
    private String message;
    private int status;
    private LocalDateTime timestamp;

    public ErrorResponse(String message, int status) {
        this.message = message;
        this.status = status;
        this.timestamp = LocalDateTime.now();
    }
}

@ControllerAdvice μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•΄ μ „μ—­ μ˜ˆμ™Έ 처리 ν•Έλ“€λŸ¬λ₯Ό κ΅¬ν˜„ν–ˆλ‹€.
기쑴의 try-catch 방식이 μ•„λ‹Œ 컨트둀러 λ‹¨μ—μ„œ λ°œμƒν•  수 μžˆλŠ” μ˜ˆμ™Έλ₯Ό 쀑앙 μ§‘μ€‘ν˜•μœΌλ‘œ μ²˜λ¦¬ν•˜κΈ° μœ„ν•œ ꡬ쑰이닀.
λΆˆν•„μš”ν•œ μ˜ˆμ™Έ 클래슀λ₯Ό λ¬΄λΆ„λ³„ν•˜κ²Œ λ§Œλ“€μ§€ μ•Šλ„λ‘ κ³ λ―Όν•˜μ—¬ ν”„λ‘œμ νŠΈμ— κΌ­ ν•„μš”ν•˜κ³  μƒκ°λ˜λŠ” μ˜ˆμ™Έλ“€λ§Œ μ»€μŠ€ν…€ μ˜ˆμ™Έλ‘œ μ •μ˜ν–ˆλ‹€.
각 ExceptionHandler λŠ” ν•΄λ‹Ή μ˜ˆμ™Έμƒν™©μ— λ§žλŠ” HTTP μƒνƒœμ½”λ“œμ™€ μ—λŸ¬ λ©”μ„Έμ§€λ₯Ό 담은 ErrorResponse λ₯Ό λ°˜ν™˜ν•˜λ„λ‘ μ„€κ³„ν•˜μ˜€λ‹€.
κΈ°λ³Έ 응닡 μ •λ³΄λ§ŒμœΌλ‘œλŠ” 무수히 λ°œμƒν•  수 μžˆλŠ” λ‹€μ–‘ν•œ μ˜ˆμ™Έ 상황을 μΌκ΄€λ˜κ²Œ κ΅¬λΆ„ν•˜κ³  μ²˜λ¦¬ν•˜κΈ° μ–΄λ ΅λ‹€.
κ·Έλ ‡κΈ° λ•Œλ¬Έμ— μ˜ˆμ™Έλ₯Ό 직접 μ •μ˜ν•˜κ³  ν•„μš”ν•œ 정보λ₯Ό λ‹΄μ•„ μ»€μŠ€ν…€ μ˜ˆμ™Έλ‘œ μ²˜λ¦¬ν•˜λŠ” 것은 κ°œλ°œμžκ°€ μ£Όλ„μ μœΌλ‘œ μ˜ˆμ™Έ 흐름을 μ„€κ³„ν•˜λŠ” μ€‘μš”ν•œ 기술이라고 μƒκ°ν•œλ‹€.


Scheduler ν”„λ‘œμ νŠΈ

https://github.com/teopteop/SchedulerProject

profile
μ°¨κ·Όμ°¨κ·Ό

0개의 λŒ“κΈ€