자동화 slecs

Spring MVC 내부 클래스 개선과 무중단 배포 안정성 확보

목차

Spring MVC 프로젝트에서 내부 클래스를 손보는 작업은 규모가 크지 않아서 간과하기 쉬운데, 실제로는 요청 처리 흐름 전체에 조용히 영향을 미친다. 이번 작업도 사용자 흐름에서 드러난 개선 포인트를 반영한 거라, 겉으로 보이는 변경량보다 신경 쓸 지점이 많았다.

내부 클래스 수정에서 챙겨야 할 것들

Spring MVC + MyBatis 구조에서 Controller 내부 클래스(또는 패키지 레벨 분리 없이 같은 파일에 묶인 보조 클래스)는 요청 파라미터 바인딩과 화면 제어를 한곳에서 담당하는 경우가 많다. 작아 보이지만 이 클래스 하나가 흔들리면 해당 기능 전체가 같이 흔들린다.

이번에 건드린 포인트를 정리하면 이렇다.

  • 파라미터 검증 및 바인딩: @RequestParam, @ModelAttribute 어노테이션 기반 바인딩에서 null·빈 값 케이스를 명시적으로 처리. Spring의 기본 바인딩은 타입 불일치에 BindException을 던지는데, 이걸 그대로 전파하면 사용자한테 500이 간다. BindingResult로 받아서 응답 형태를 결정하는 패턴으로 정리했음.
  • 비즈니스 로직 위치: 내부 클래스 안에 너무 많은 로직이 뭉쳐있으면 나중에 테스트도 힘들고 재사용도 안 된다. 이번엔 Service 레이어 호출로 위임하는 구조가 명확한지 다시 확인했음.
  • SQL 쿼리 관리: MyBatis XML에서 resultMap 매핑이 컬럼명 변경에 취약한 부분이 있어서 alias 기준으로 정리. 컬럼 순서가 아니라 이름 기반으로 매핑되도록 확인했음.

코드 패턴 예시로, 파라미터 검증 실패 시 흐름을 명확하게 분리하는 방식은 이렇게 쓴다.

@PostMapping("/example")
public ResponseEntity<?> handle(@Valid @ModelAttribute RequestDto dto,
                                 BindingResult result) {
    if (result.hasErrors()) {
        // 400으로 내리고 에러 메시지 변환
        return ResponseEntity.badRequest()
                             .body(result.getAllErrors());
    }
    return ResponseEntity.ok(service.process(dto));
}

예외 케이스 처리에서 선택지가 두 갈래다. RuntimeException을 위로 전파해서 @ControllerAdvice에서 일괄 처리할 건지, 아니면 해당 메서드 안에서 잡아서 응답을 직접 구성할 건지. 이번엔 기존 @ControllerAdvice가 이미 있어서 전파 방식을 유지했는데, 새로 추가한 예외 케이스만 핸들러에 등록해두었다.

트랜잭션 범위는 Service 레이어의 @Transactional 경계를 다시 확인했다. Controller에서 직접 DB를 건드리는 코드가 섞여있으면 트랜잭션이 원하는 범위로 안 잡히는 경우가 있어서, 로직을 Service 메서드 한 군데로 모아두는 게 안전하다.

로그는 운영에서 실제로 디버깅할 수 있게 남겨두는 게 포인트다. 입력값과 처리 결과를 INFO 레벨에 남기면, 장애가 나도 로그 레벨 변경 없이 추적이 된다. DEBUG에만 박아두면 운영 환경에서는 아무것도 안 보인다.

무중단 배포, 어디서 실수가 나는가

코드 수정 후 배포 순서는 로컬 기동 확인 → 개발 DB 반영 → 배포로 진행했다. 단계 자체는 단순한데, 실제로 문제가 나는 건 "기동 확인"을 제대로 안 하고 넘어갈 때다.

무중단 배포에서 트래픽 전환 타이밍이 핵심이다. 신규 버전이 프로세스 레벨에서 떠있어도 실제로 정상 응답을 내놓기 전까지는 트래픽을 받으면 안 된다. 헬스체크 엔드포인트가 여기서 쓰인다.

단계 확인 포인트 실패 시
신규 버전 기동 헬스체크 엔드포인트 200 응답 트래픽 전환 보류
프록시 설정 변경 요청이 신규 인스턴스로 라우팅 즉시 롤백
구버전 셧다운 처리 중 요청 drain 완료 graceful shutdown 대기

헬스체크 엔드포인트는 단순히 HTTP 200만 리턴하는 게 아니라, DB 커넥션이나 의존 서비스가 실제로 응답하는지까지 확인하는 게 낫다. 앱이 떠있어도 DB 커넥션 풀이 죽어있으면 어차피 서비스 불능 상태라서, 헬스체크에서 잡아야 한다.

롤백 준비도 배포 전략의 일부다. 이전 버전 JAR나 이미지를 바로 올릴 수 있게 남겨두고, 배포 직후 5-10분은 모니터링을 유지하는 게 관행이다. 문제가 나면 빨리 복구하는 게 원인 분석보다 우선이다. 분석은 복구 후에 해도 된다.

이번 작업은 기능 자체보다 기존 코드의 안정성과 유지보수성을 높이는 방향이 컸다. 작은 수정들이 쌓여서 나중에 큰 장애를 막는다는 걸, 이런 작업 할 때마다 다시 체감한다. 화려한 신기능보다 이런 정리 작업이 운영 안정성에서는 더 직접적인 효과가 있다.

댓글 0

첫 댓글 달아줘.