[강의] Spring 안정성 높이기

Jerry·2025년 9월 17일

애플리케이션 안정성 개요

실제 웹 서비스에서 발생하는 다양한 문제 상황

각 문제 상황은 실제 웹 서비스 운영에서 자주 발생하는 대표적인 오류 유형이다. 입력 검증 실패, 예외 처리 부족, 자원 고갈, 외부 연동 장애 등은 서비스 안정성과 신뢰성을 저하시킬 수 있다.

구분개념설명
잘못된 입력값 유입사용자가 의도적·비의도적으로 유효하지 않은 데이터를 전송하는 상황형식 오류, 범위 초과 등으로 인해 애플리케이션이 정상적으로 동작하지 않는 상황
예상치 못한 예외 발생코드에서 처리되지 않은 런타임 오류가 발생하는 상황NullPointer, DB 연결 오류 등으로 서비스 로직이 중단되는 상황
시스템 리소스 부족CPU·메모리·디스크·네트워크 등 한정된 자원이 초과 사용되는 상황자원 고갈로 응답 지연이나 장애가 발생하는 상황
외부 시스템 연동 실패결제·인증·제3자 API 등 외부 서비스와의 통신 오류요청 실패 또는 지연으로 내부 서비스까지 영향이 전파되는 상황

문제 상황- 잘못된 입력값 유입

사용자가 의도적· 비의도적으로 유효하지 않은 데이터(형식 오류, 범위 초과 등)를 전송하여 애플리케이션이 정상적으로 동작하지 않는 상황이다. 일반적으로 프론트엔드에서 입력값을 검증하고 차단하지만, 보안과 안정성을 위해 백엔드 애플리케이션 계층이나 데이터베이스 계층에서도 유효성 검증을 수행하여 잘못된 데이터의 저장 및 처리를 방지할 수 있다.

잘못된 입력 값 유입 패턴

패턴설명대응 방안
사용자 실수오타, 필수값 누락, 잘못된 형식프론트 검증(필수 체크·형식 안내) + 백엔드/DB 제약(Bean Validation, UNIQUE/CHECK)
SQL 인젝션 / 스크립트 주입악성 코드 삽입으로 정보 유출·변조파라미터 바인딩(PreparedStatement/ORM), 입력 패턴 검사, 출력 이스케이프(HTML/JS)
범위 초과 입력음수, 과도한 수치 등 비정상 값 입력입력 제한(최소·최대), 유효성 검사(@Min/@Max, DB CHECK 제약)
중복 요청결제·주문 중복 처리 발생멱등 키(토큰), 유니크 제약, 서버 측 중복 방지 락/디듀프
스키마 불일치필드 누락·타입 오류스키마 검증(JSON Schema/OpenAPI), 타입 제약(요청 DTO 검증)

문제 상황- 예상치 못한 예외 발생

코드에서 처리되지 않은 런타임 오류(NullPointer, DB 연결 오류 등)가 발생해 서비스 로직이 중단되는 상황
예외는 프로그래밍 상으로 충분히 발생 할 수 있지만, 이러한 예외를 사용자에게 노출한 경우 보안 위협이 발생 할 수 있음으로 일관성 있는 예외 처리가 필요하다.

문제 상황- 시스템 리소스 부족

CPU, 메모리, 디스크, 네트워크 등 한정된 자원이 초과 사용되어 응답 지연이나 장애가 발생하는 상황
→ 초반 성능 테스트 이후 충분한 HW가 보장된다 할지라도 프로그램 에러나 HW 고장으로 발생 할 수 있는 요소
운영 모니터링 및 복원력(Resilience) 향상으로 대응 가능

구분설명대응 방안
CPU 과부하무한 루프, 비효율적 연산, 대량 요청으로 처리 지연 발생효율적 알고리즘 적용, 스레드풀/큐 크기 제한, 서버 확장(오토스케일링)
메모리 부족객체 누수, 캐시 남용, 대용량 적재로 OOM 발생누수 점검(프로파일링), 캐시 용량·TTL 제한, 스트리밍 처리, GC 튜닝/힙 조정
디스크 부족/과다 I/O로그 폭증, 대용량 파일, 인덱스 부재로 성능 저하로그 로테이션, 불필요 데이터 정리, 인덱스 최적화/리빌드
네트워크 대역폭 초과접속자 폭증, 대용량 응답, DDoS로 지연 발생CDN, Rate Limiting, DDoS 방어(웹 방화벽), 응답 압축/캐싱
공통 대응(모니터링)모니터링으로 장애를 빠르게 발견·회복Spring Boot Actuator(health/metrics), 외부 모니터링 연동(ELK/Grafana/Prometheus), Amazon CloudWatch 연동

문제 상황- 외부 시스템 연동 실패

결제, 인증, 제3자 API 등 외부 서비스와 통신 오류가 발생하여 요청이 실패하거나 지연되는 상황이다.
일반적으로 Resilience4j를 통한 복원력 강화로 해결 가능하다.

구분설명대응 방안
타임아웃 관리외부 서비스 응답 지연으로 호출이 길어지는 상황요청 타임아웃 설정(연결/읽기 분리), 빠른 실패 처리(취소·정리 로직)
재시도 + 백오프일시적 오류로 호출 실패지수 백오프 재시도, 재시도 횟수 제한, 대상 한정(5xx/네트워크 오류만)
회로 차단 (Circuit Breaker)외부 장애가 연속 발생해 내부로 전파실패율·슬로우율 기준 차단, 열림→반열림→닫힘 전이, 차단 중 즉시 실패
폴백 (Fallback)외부 의존성 문제로 기능이 중단캐시/기본값 반환, 대체 경로 안내, 사용자 친화 오류 메시지 제공

안정성 확보를 위한 네 가지 핵심 요소

구분설명활용 도구
예외 처리런타임 오류 발생 시 서비스 중단을 방지하고 사용자 친화적 응답 제공@ControllerAdvice, @ExceptionHandler
로깅실행 흐름과 오류를 기록하여 원인 분석·추적 지원SLF4J, Logback, Log4j2
입력값 검증잘못된 데이터 유입 차단으로 보안·안정성 강화Bean Validation(JSR-380), @Valid, @NotNull, @Size
모니터링상태·성능·리소스 사용량 관찰로 장애 조기 탐지Spring Boot Actuator, Micrometer, Prometheus, Grafana

예외 처리의 이해

예외처리(Exception Handling)란?

예외처리는 실행 중 발생할 수 있는 비정상 상황(에러나 오류)을 정상적으로 제어하기 위한 기법이다.
에러를 무시하거나 프로그램이 중단되지 않도록 하고, 사용자에게 의미 있는 응답을 제공할 수 있도록 한다. Java에서는 try-catch-finally 구문이나 throw/throws 키워드를 통해 예외를 명시적으로 처리한다.

public class ExceptionExample {
	public static void main(String[] args) {
		try {
			int result = 10 / 0; // 예외 발생 코드
			System.out.println("결과: " + result);
		} catch (ArithmeticException e) {
			System.out.println("예외 발생: " + e.getMessage());
			e.printStackTrace();
		} finally {
			System.out.println("프로그램 종료");
		}
	}
}

예외 처리의 필요성

구분내용활용 기술
안정적인 서비스 운영전역 예외 처리로 컨트롤러 단의 오류를 일관되게 흡수하여 서비스 중단 방지@ControllerAdvice, @ExceptionHandler, ResponseEntityExceptionHandler, HandlerExceptionResolver
사용자 피드백 제공상태코드·메시지·에러코드·필드 오류를 구조화해 이해 가능한 응답 제공@ResponseStatus, 표준 오류 응답 스펙 설계
시스템 장애 예방 및 추적요청 단위 추적·징후 모니터링, 서킷브레이커·재시도로 장애 전파 차단 및 원인 추적로깅(Logback), Actuator, Resilience4j
디버깅 정보 수집검증/서버 예외 메타데이터, 요청·응답 페이로드, 사용자·트레이스 정보 기록으로 재현성 향상로깅(Logback), 커스텀 에러, Actuator

Spring의 예외 처리 아키텍처

Spring의 예외 처리 아키텍처는 DispatcherServlet이 컨트롤러 실행 중 발생한 예외를 받아 HandlerExceptionResolver 체인에 위임하고, 적절한 리졸버가 예외를 처리하여 ModelAndView나 응답을 반환하는 구조로 구성되어 있다.
만일, 에러체인이 적용되어 있지 않은 경우 DispatcherServlet이 WAS(Tomcat)으로 전파하여 WAS가 에러처리를 수행한다.

ExceptionHandlerExceptionResolver는 @ExceptionHandler 메서드와 @ControllerAdvice나 @RestControllerAdvice에 등록된 전역 핸들러를 탐색해 실행한다. 즉, Resolver가 예외를 가로채고 Advice가 전역 예외 처리 메서드를 제공하여 일관된 예외 응답을 가능하게 한다.

  • HandlerExceptionResolver: 예외를 가로채어 알맞은 핸들러(@ExceptionHandler)나 기본 전략으로 변환하는 처리기
  • Advice: 컨트롤러 전역에 적용되는 예외 처리/데이터 바인딩/모델 속성 관리 컴포넌트

예외 처리 아키텍처- 예외 추상화의 필요성

Spring Boot에서는 데이터 접근 기술과 무관하게 일관된 예외 계층(DataAccessException)으로 처리할 수 있다.
DB 벤더나 기술별 예외를 추상화하여 코드 변경 없이 동일한 로직으로 대응 가능하다.
예외 성격(일시적/영구적)을 구분해 재시도, 사용자 메시지, 로깅 정책을 체계적으로 적용할 수 있다.

예외 클래스 계층 구조

Exception과 Error 클래스 모두 Object 클래스의 자손이며 모든 예외의 최고 조상은 Exception 클래스
반드시 예외 처리해야 하는 Checked Exception과 해주지 않아도 되는 Unchecked Exception으로 나뉨

  • Error: OutOfMemoryError, StackOverflowError 등 JVM 관련 오류, 예외처리로 해결 불가
  • Checked Exception: IOException, SQLException 등 컴파일 시 반드시 예외 처리 필요
  • Unchecked Exception (RuntimeException 하위): NullPointerException, IllegalArgumentException 등 예외처리가 강제화 안됨

예외 클래스 종류

Error

오류 클래스설명
OutOfMemoryErrorJVM 힙 메모리가 부족할 때 발생
StackOverflowError재귀 호출이 너무 깊어 스택이 넘칠 때 발생
VirtualMachineErrorJVM이 내부적으로 심각한 문제를 만났을 때(치명적 오류의 상위 클래스)
NoClassDefFoundError컴파일은 성공했지만 실행 중 클래스 로딩 실패(클래스패스 누락/초기화 실패 등)
InternalErrorJVM 내부 버그로 인해 발생하는 오류

Checked Exception

예외 클래스설명
IOException입출력 작업 중 오류(파일, 네트워크 스트림 등)
FileNotFoundException존재하지 않는 파일을 열거나 접근할 때 발생 (IOException 하위)
SQLExceptionDB 연결·쿼리 실행 등 JDBC 작업 중 오류
ParseException문자열 파싱 실패(날짜/숫자/커스텀 포맷 등)
ClassNotFoundExceptionClass.forName() 등으로 클래스 로딩 실패(컴파일 성공 → 런타임 클래스패스 문제)
InterruptedException대기/슬립/블로킹 중인 스레드가 인터럽트될 때 발생
MalformedURLException잘못된 URL 형식(프로토콜/포맷 오류) (IOException 하위)

Unchecked Exception

예외 클래스설명
NullPointerExceptionnull 참조에 접근(메서드 호출, 필드 접근, 언박싱 등)할 때 발생
ArrayIndexOutOfBoundsException배열 인덱스가 범위를 벗어날 때(음수 또는 length 이상) 발생 (IndexOutOfBoundsException 하위)
IndexOutOfBoundsException리스트/문자열 등에서 유효 범위를 초과한 인덱스 접근 시 발생
ArithmeticException0으로 나누기 등 산술 연산 오류 발생
IllegalArgumentException메서드에 부적절한 인자 전달 시 발생 (NumberFormatException 포함)
IllegalStateException객체의 현재 상태가 메서드 호출에 부적합할 때 발생
ClassCastException호환되지 않는 타입으로 형변환 시 발생
NumberFormatException숫자가 아닌 문자열을 숫자로 파싱할 때 발생 (IllegalArgumentException 하위)
UnsupportedOperationException지원되지 않는 기능을 호출할 때 발생(예: 불변 컬렉션 수정 시)
ConcurrentModificationException반복 중 컬렉션을 구조적으로 변경할 때 발생(FAIL-FAST 이터레이터)

Spring Framework Unchecked Exception

예외 클래스설명
DataAccessException데이터 접근 계층에서 발생하는 모든 예외의 루트 클래스
CleanupFailureDataAccessException리소스 정리(clean-up) 과정에서 오류 발생 시
DataIntegrityViolationException데이터 무결성 제약 조건 위반 시(UNIQUE, FK 등)
DataRetrievalFailureException특정 데이터 조회 실패 시
DeadlockLoserDataAccessExceptionDB 데드락으로 인해 현재 트랜잭션 실패 시
IncorrectResultSizeDataAccessException예상한 결과 개수와 다른 경우(예: 단일 조회인데 여러 건 반환)
IncorrectUpdateSemanticsDataAccessException잘못된 Update 실행 결과 발생 시
InvalidDataAccessApiUsageException잘못된 DAO API 사용 시
InvalidDataAccessResourceUsageException잘못된 리소스 사용 시(잘못된 SQL 구문 등)
PermissionDeniedDataAccessException접근 권한이 없어 실행 불가 시
TransientDataAccessException일시적인 예외 발생(재시도 가능)
QueryTimeoutException쿼리 실행 시간 초과 시
TypeMismatchDataAccessExceptionJDBC 타입 불일치 시
UncategorizedDataAccessException분류되지 않은 기타 데이터 접근 예외

예외 처리 아키텍처- 예외 변환 메커니즘

예외 변환 메커니즘은 하위 계층에서 발생한 기술 종속적인 예외를 상위 계층에서 이해할 수 있는 추상화된 예외로 변환하는 과정이다. 이를 통해 DB, 네트워크 등 다양한 자원 예외를 일관된 방식으로 처리하고, 서비스·컨트롤러 계층에서는 표준화된 예외 처리 로직만 유지하면 된다.즉, 내부 구현 변경에도 불구하고 외부 계층은 동일한 예외 처리 계약을 사용할 수 있게 한다.

효과적인 예외 처리 구현

통합 예외 처리 전략

1) @RestControllerAdvice 활용

  • 전역적으로 발생하는 예외를 하나의 클래스로 통합 관리한다.
  • 모든 컨트롤러에 공통적으로 적용되며, 중복 처리를 줄일 수 있다.

2) 일관된 오류 응답 구조 설계

  • 에러 응답을 통일된 JSON 형태로 정의하여 클라이언트에서 일관성 있게 처리 가능하게 한다.
  • 예외 처리 시 공통 ErrorResponse 객체를 반환하도록 한다.

로깅 필요성과 기본 설정

로깅(Logiing)이란?

로깅(Logging)은 애플리케이션 동작 과정에서 발생하는 다양한 이벤트, 상태, 오류 정보를 기록하는 행위이다.
운영 중 문제 원인을 추적하고 성능을 분석하며 보안· 감사를 위해 필수적인 근거 자료를 제공한다.
이를 통해 개발자는 디버깅, 모니터링, 장애 대응을 효과적으로 수행할 수 있다.

로깅(Logiing) 도구의 종류

구분설명사용 환경권장 여부
SLF4J (Facade)로깅 추상화 계층. 구현체(Logback·Log4j2 등)를 유연하게 교체 가능모든 Spring Boot 프로젝트의 표준 로깅 API필수
LogbackSpring Boot 기본 구현체. 설정 간단, 성능 우수일반 웹/마이크로서비스, 대부분의 서비스권장
Log4j 2Log4j 개선판. 비동기 로깅(Async) 지원, 다양한 설정(XML/YAML/JSON)고부하·대규모 트래픽, 실시간 스트리밍조건부 권장 (고성능 요구 시)
Log4j 1.x과거 많이 사용. 현재 EOL(지원 종료)로 보안 취약점 존재레거시 시스템 유지보수 목적비권장 (신규 사용 금지)
java.util.logging (JUL)JDK 기본 로깅. 단순하지만 기능 제한레거시 통합, 경량 환경비권장

로깅의 필요성

구분설명
애플리케이션 동작 상태 파악시스템의 정상 동작 여부, 성능 지표, 서비스 처리 흐름을 기록하여 운영자가 상태를 실시간으로 파악할 수 있게 함
문제 상황 분석과 해결오류·예외 발생 시 로그로 원인을 추적해 디버깅 및 문제 해결 시간을 단축함
비즈니스 이벤트 추적사용자 활동, 주문 처리, 결제 내역 등 비즈니스 단위 이벤트를 기록해 품질 관리와 데이터 분석에 활용함
보안 감사(Audit)접근 이력, 권한 변경, 민감 정보 접근 등을 기록해 보안 침해 탐지와 컴플라이언스 준수에 활용함

Logback 기본 설정

  1. 로그 레벨 정책
    • 기본 로그 레벨은 INFO이며, 별도 지정하지 않으면 모든 로거가 INFO 이상만 출력된다.
    • 패키지 단위 혹은 특정 클래스별로 세분화하여 설정할 수 있다.
    • 계층적 구조를 따르므로, 상위 패키지에 적용하면 하위 패키지에도 적용된다.
    • 우선순위: TRACE < DEBUG < INFO < WARN < ERROR
  2. 로그 출력 패턴
    • Spring Boot는 기본적으로 콘솔 패턴을 제공한다.
    • YAML 설정에서 logging.pattern.console 속성으로 커스터마이징 가능하다.
    • 패턴 구성 요소 예시:
      %d{yyyy-MM-dd HH:mm:ss.SSS} : 날짜/시간
      %-5level : 로그 레벨 (고정 길이 5칸)
      %thread : 스레드명
      %logger{36} : 로거명(클래스명), 최대 36자
      %msg : 로그 메시지
  3. 파일 출력과 관리 정책
    • 기본은 콘솔 출력만 제공되며, 파일 로그는 직접 지정해야 한다.
    • logging.file.name 또는 logging.file.path로 파일을 활성화한다.
      logging.file.name: 로그 파일 이름 지정
      logging.file.path: 디렉토리 지정 (기본 파일명은 spring.log)

환경별 로깅 전략

  1. 개발/운영 환경 설정 분리
    • 개발 환경: 디버깅 편의성을 위해 DEBUG 레벨 이상 출력, 콘솔 중심 출력
    • 운영 환경: 성능과 보안을 위해 INFO 또는 WARN 이상 출력, 파일·외부 수집 시스템(ELK, Prometheus) 사용
    • Spring Boot에서는 application-dev.yml, application-prod.yml로 나눠 환경별 로깅 속성을 정의 가능
구분개발 환경(Development)운영 환경(Production)
출력 대상콘솔 중심 (Logback/Log4j)파일 로그 + 외부 수집 시스템(ELK, Grafana, Prometheus)
로그 레벨DEBUG 또는 TRACE (상세 정보 포함)INFO 또는 WARN 이상 (필요 최소한)
로그 관리짧은 주기로 롤링, 불필요한 공간 낭비 방지용량/기간 기반 롤링, 장기 보관 정책 적용
활용 목적디버깅 편의성, 문제 원인 빠른 진단성능 최적화, 보안 감사, 운영 안정성
Spring Boot 설정application-dev.ymlapplication-prod.yml
  1. 로그 파일 관리
    • 운영 환경에서는 파일 출력 + 롤링 정책 필수
    • 날짜별/용량별 분할 저장하여 로그 크기를 제어
    • 보관 기간(예: 30일) 설정으로 저장 공간 관리 및 보안 규정 준수
  2. 로그 레벨 조정
    • 개발 환경: SQL, HTTP 요청/응답 로그를 DEBUG로 출력해 문제 해결 속도 향상
    • 운영 환경: 핵심 서비스 로그는 INFO, 오류 로그는 ERROR, 프레임워크/라이브러리는 WARN으로 제한해 불필요한 로그 억제
    • 상황별로 패키지 단위 레벨을 세밀하게 조정 가능

효과적인 로깅 구현

로그 레벨 별 활용 전략

각 로그 레벨의 목적과 사용 시점

로그 레벨목적사용 시점
TRACE가장 상세한 로그, 내부 흐름을 단계별로 추적메서드 진입/반환, 루프 내부 값 확인 등 세밀한 디버깅 상황
DEBUG개발자가 디버깅할 때 필요한 정보 제공SQL 쿼리 출력, 주요 변수 값 확인, 조건 분기 결과 등 (개발 환경 전용)
INFO시스템의 정상 동작을 알리는 운영 정보서비스 시작/종료, 중요한 비즈니스 이벤트(회원 가입, 주문 완료 등)
WARN잠재적 문제를 경고(즉시 장애는 아님)Deprecated API 호출, 성능 저하 가능성, 임계치 접근 상황
ERROR비정상 상황으로 기능 실패를 알림예외 발생, DB 연결 실패, 외부 서비스 호출 실패 등 (운영 대응 필요)

로그 메시지 작성 원칙

간결하고 명확하게: 로그 메시지는 한눈에 이해할 수 있도록 작성
맥락 포함: 어떤 작업 중, 어떤 데이터에 대해 발생했는지 기술
민감 정보 제외: 비밀번호, 주민등록번호, 카드번호 등은 로깅 금지
일관된 형식: 동일한 구조(Action - 대상- 결과/상태)로 작성해 분석 용이성 확보

예외 상황 로깅

예외 스택트레이스 출력은 반드시 ERROR 레벨에서 사용
메시지와 함께 비즈니스 컨텍스트(orderId, userId 등)를 같이 남겨야 문제 추적 용이
단순 사용자 입력 오류는 WARN, 시스템 오류(DB 연결 실패 등)는 ERROR로 구분
throw 전에 로깅하고, 불필요한 중복 로그는 피함

로그 분석과 활용

1) 주요 로깅 포인트

  • 애플리케이션 라이프사이클: 시작/종료 시점, 주요 컴포넌트 초기화 정보
  • 비즈니스 이벤트: 사용자 로그인, 주문 생성, 결제 완료 등 주요 도메인 이벤트
  • 외부 시스템 연동: API 호출 요청/응답, DB 쿼리, 메시지 큐 송수신 결과
  • 예외/오류 발생 지점: 예외 발생 시점, 원인, 컨텍스트 데이터(사용자 ID, 요청 파라미터 등)
    → 운영자가 서비스 흐름과 문제 상황을 빠르게 파악할 수 있도록 필요한 위치에 로그 삽입

2) 로그 검색과 분석

  • 검색 도구 활용: ELK(Elasticsearch + Logstash + Kibana), Grafana, Splunk 등을 사용하여 로그를 중앙집중화
  • 패턴 기반 검색: 키워드(예: orderId=1234)나 로그 레벨별 필터링(DEBUG/ERROR)로 특정 문제 추적
  • 메트릭화: 오류 빈도, 응답 시간, WARN 이상 발생 횟수를 시각화하여 SLA 준수 여부 점검
  • 상관 분석: 여러 서비스 로그를 traceId, correlationId로 묶어 분산 환경에서 호출 흐름 추적

3) 문제 해결을 위한 활용

  • 디버깅: 로그 타임스탬프와 메시지를 비교해 오류 발생 직전 상태를 파악
  • 장애 대응: ERROR 로그를 통해 원인 분석 및 복구 시간 단축 (MTTR 감소)
  • 보안/감사: 사용자 접근 로그, 민감 자원 접근 이력을 남겨 이상 행위 탐지 및 규제 준수 지원
  • 성능 최적화: 응답 시간, 쿼리 수행 시간 로그를 분석해 병목 지점 파악

Bean Validation 이해와 기본

Bean Validation의 개념

Bean Validation은 자바 애플리케이션에서 객체의 속성 값 검증을 표준화하기 위한 스펙(JSR 380, JSR 303)이다.
어노테이션 기반으로 필드 제약 조건(@NotNull, @Size, @Email 등)을 선언하여 중복 코드 없이 일관된 검증 로직을 제공한다. Spring Boot와 통합 시 Controller, Service, Persistence 계층까지 자동 검증과 예외 처리를 지원

데이터 검증의 필요성

구분설명
잘못된 데이터 유입 방지유효하지 않은 값이 시스템 내부로 들어오는 것을 차단하여 오류와 보안 취약점을 예방
일관된 검증 로직 관리애플리케이션 전반에서 동일한 검증 규칙을 적용해 중복 코드와 관리 비용을 줄임
비즈니스 규칙 강제도메인 규칙(예: 나이 제한, 이메일 형식 등)을 코드 레벨에서 강제하여 데이터 무결성 확보
명확한 에러 메시지 제공검증 실패 시 사용자에게 구체적이고 이해하기 쉬운 피드백을 제공해 문제 해결을 지원

Bean Validation 주요 어노테이션

어노테이션설명주 사용 위치사용 예시
@ValidJSR-303 표준 검증 트리거. 객체·중첩 객체까지 연쇄(cascaded) 검증Controller 파라미터, DTO 필드public Response create(@Valid @RequestBody UserDto dto)
@ValidatedSpring 전용. 그룹 기반 검증·메서드 레벨 검증 지원Controller/Service 클래스·메서드@Validated(Create.class)
@NotNull값이 null이면 안 됨필드, 파라미터@NotNull private String name;
@NotEmptynull·빈 문자열("")·빈 컬렉션 불가문자열/컬렉션 필드@NotEmpty private List<String> items;
@NotBlanknull·빈 문자열·공백만 있는 문자열 불가문자열 필드@NotBlank private String title;
@Min / @Max숫자 값의 최소/최대 범위 제한숫자 필드@Min(1) @Max(100) private int age;
@Positive / @Negative양수/음수만 허용숫자 필드@Positive private Long price; / @Negative private Integer change;
@Size문자열/컬렉션 길이(개수) 제한문자열/컬렉션 필드@Size(min=2, max=20) private String nickname;
@Email이메일 형식 검증문자열 필드@Email private String email;
@Pattern정규식으로 형식 검증문자열 필드@Pattern(regexp="^[0-9]{3}-[0-9]{4}$") private String phone;
@Past / @Future과거만 / 미래만 허용날짜·시간 필드@Past private LocalDate birthday; / @Future private LocalDate dueDate;
@PastOrPresent / @FutureOrPresent과거 또는 현재 / 미래 또는 현재 허용날짜·시간 필드@FutureOrPresent private LocalDate startDate;

Bean Validation 심화

커스텀 Validator 구현

커스텀 Validator는 기본 제공 검증으로 처리할 수 없는 도메인 특화 검증 규칙을 직접 정의하는 컴포넌트이다.
ConstraintValidator 인터페이스를 구현해 어노테이션과 함께 사용하며, 객체 필드나 요청 값에 대해 맞춤형 유효성 검사를 수행한다.

1) 커스텀 제약조건 정의

  • 검증 대상에 부착할 애노테이션을 만들고, @Constraint(validatedBy=...)로 처리할 Validator 클래스를 연결한다
  • 기본 메시지, 그룹, payload 등을 지정하며, 국제화를 위해 message에는 메시지 키를 사용한다
@Documented
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = StrongPasswordValidator.class)
public @interface StrongPassword {
    String message() default "{error.password.strong}"; // 메시지 키
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

	int min() default 8; // 커스터마이즈 가능한 속성
    boolean requireUpper() default true;
    boolean requireLower() default true;
    boolean requireDigit() default true;
    boolean requireSpecial() default true;
}

2) Validator 클래스 구현

  • ConstraintValidator<애노테이션, 타입>를 구현하고 initialize로 옵션을 캐싱한 뒤 isValid에서 검증한다.
  • null 허용 정책을 명확히 한다: 보통 null은 다른 제약(@NotBlank 등)과 조합하므로 여기서는 true로 반환한다.
public class StrongPasswordValidator implements ConstraintValidator<StrongPassword, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
    	if (value == null || value.isBlank()) return false;
    	return value.length() >= 8
    		&& value.chars().anyMatch(Character::isUpperCase)
    		&& value.chars().anyMatch(Character::isLowerCase)
    		&& value.chars().anyMatch(Character::isDigit);
    }
}

3) 검증 로직 작성(적용 & 메시지)

  • DTO/메서드 파라미터에 커스텀 제약을 부착하고, 컨트롤러에서 @Valid/@Validated로 작동시킨다.
  • 고객에게 전달될 메시지는 ValidationMessages.properties에 관리한다.
public record SignUpRequest(
	@NotBlank(message = "이메일을 입력해 주시기 바랍니다.")
	@Email(message = "올바른 이메일 주소를 입력해 주시기 바랍니다.")
	String email,

	@NotBlank(message = "비밀번호를 입력해 주시기 바랍니다.")
	@StrongPassword(min = 10, message = "{error.password.strong}") // 메시지 키 사용
	String password
) {}

복합 객체 검증

1) 중첩된 객체 검증

  • DTO 안에 다른 객체가 포함된 경우, 내부 객체에도 검증을 전파해야 한다.
  • 이때 @Valid를 필드에 붙여야 하위 객체의 제약조건까지 함께 검증된다.
public class Address {
	@NotBlank(message = "도로명은 반드시 입력해 주셔야 합니다.")
	private String street;

	@NotBlank(message = "도시는 반드시 입력해 주셔야 합니다.")
	private String city;
}

public class UserRequest {
	@NotBlank(message = "이름은 반드시 입력해 주셔야 합니다.")
	private String name;

	@Valid
	private Address address; // 중첩 객체도 검증
}

2) 컬렉션 요소 검증

  • List, Set, Map 같은 컬렉션 내부 요소도 검증 가능하다.
  • 제네릭 타입 앞에 @Valid나 제약 애노테이션을 붙여 개별 요소까지 체크한다.
public class OrderRequest {
	@NotBlank
	private String orderId;

	@Size(min = 1, message = "최소 한 개 이상의 상품이 필요합니다.")
	private List<@Valid ProductRequest> products; // 리스트 요소 검증
}

public class ProductRequest {
	@NotBlank(message = "상품명은 비워 둘 수 없습니다.")
	private String name;

	@Positive(message = "가격은 양수여야 합니다.")
	private int price;
}

3) 상황별 검증 전략

  • 같은 객체라도 등록(Create) / 수정(Update) 상황에 따라 검증 규칙이 달라질 수 있다.
  • 이때는 @Validated(Group.class) 방식으로 그룹 검증을 적용한다.
public class UserDto {
	public interface Create {}
	public interface Update {}

	@NotNull(groups = Update.class, message = "수정 시에는 ID가 필요합니다.")
	private Long id;

	@NotBlank(groups = {Create.class, Update.class}, message = "이름은 필수 입력 값입니다.")
	private String name;
}

@RestController
public class UserController {
	@PostMapping("/users")
	public ResponseEntity<?> create(@Validated(UserDto.Create.class) @RequestBody UserDto dto) {
		...
	}

	@PutMapping("/users")
	public ResponseEntity<?> update(@Validated(UserDto.Update.class) @RequestBody UserDto dto) {
		...
	}
}

Spring Actuator 이해와 활용

Spring Actuator의 개념

Spring Boot Actuator는 애플리케이션의 상태, 메트릭, 로그, 환경 정보를 확인하고 관리할 수 있는 모니터링 도구이다. 운영 환경에서 서비스의 헬스체크, 성능 지표 수집, 모니터링 시스템 연계(Prometheus, Grafana 등)나 클라우드 자체 Watch에서 연동하여 활용된다.

모니터링의 필요성

구분설명
실시간 상태 모니터링애플리케이션·서버·네트워크의 현재 상태를 실시간으로 관찰해 성능 저하나 장애를 즉시 감지
잠재적 문제 조기 발견리소스 사용량·에러 패턴을 분석해 문제가 커지기 전에 선제 대응 가능하도록 지원
주요 지표 수집과 분석CPU·메모리·응답 시간·트랜잭션 처리량 등 핵심 지표를 수집·분석해 성능 최적화와 용량 계획에 활용
정상 동작 여부 확인구성 요소의 정상 동작을 주기적으로 점검해 서비스 가용성과 안정성을 보장

Actuator 기본 설정

  1. 엔드포인트 활성화
    • Actuator 의존성을 추가하면 일부 엔드포인트(/actuator/health, /actuator/info)는 기본 활성화된다.
    • 다른 엔드포인트(metrics, env, beans 등)는 application.yml에서
      management.endpoints.web.exposure.include로 지정해야 한다.
# build.gradle
dependencies {
	implementation 'de.codecentric:spring-boot-admin-starter-client:3.4.5'
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
}

# application.yml
management:
  endpoints:
	web:
	  exposure:
		include: health, info, metrics, env # 활성화할 엔드포인트 목록
  1. 기본 설정 구성
    • 기본적으로 서버 포트 공유: 기존 애플리케이션 포트에서 /actuator/* 경로로 접근
    • 별도 포트로 분리 가능 (management.server.port) → 보안상 운영환경에서 자주 사용
    • 헬스체크 기본 구성: DB, 디스크, 메시징 시스템 등 자동 감지
  2. 정보 노출 범위
    • 보안상 민감한 정보를 노출하지 않도록 범위 제어 필요
    • management.endpoint.health.show-details 옵션으로 Health 엔드포인트의 정보 노출 범위를 설정
      never: 항상 단순 UP/DOWN만 반환
      when-authorized: 인증된 사용자만 상세 정보 확인 가능
      always: 모든 상세 정보 노출

핵심 기능 활용

  1. Health Check 구현
    • Actuator의 /actuator/health 엔드포인트는 애플리케이션의 생존 여부를 확인하는 핵심 기능이다.
    • DB, 캐시, 메시지 브로커 등 주요 컴포넌트의 상태를 자동 감지하여 함께 리포트한다.
    • 필요 시 HealthIndicator를 구현해 커스텀 헬스체크를 추가할 수 있다.
@Component
public class CustomHealthIndicator implements HealthIndicator {

	@Override
	public Health health() {
		boolean serverUp = checkExternalServer();
		if (serverUp) {
			return Health.up().withDetail("ExternalServer", "Available").build();
		}
		return Health.down().withDetail("ExternalServer", "Unavailable").build();
	}

	private boolean checkExternalServer() {
		// 외부 서버 Ping 체크 로직
		return true;
	}
}
  1. 애플리케이션 정보 관리
    • /actuator/info 엔드포인트를 통해 애플리케이션 버전, 빌드 정보, 개발자 정보를 제공할 수 있다.
    • application.ymlinfo.* 속성을 정의하거나, InfoContributor를 구현해 동적으로 정보 제공 가능하다.
# application.yml
info:
  app:
	name: codeit-service
	version: 1.0.0
  developer:
	name: Hae Lee
	email: test@email.com
@Component
public class CustomInfoContributor implements InfoContributor {
	@Override
	public void contribute(Info.Builder builder) {
		builder.withDetail("custom", Map.of("status", "running", "uptime", "1024s"));
	}
}
  1. 메트릭 수집과 확인
    • /actuator/metrics 엔드포인트에서 CPU 사용량, JVM 메모리, HTTP 요청 수, DB 커넥션 풀 상태 등의 지표를 확인 가능하다.
    • Micrometer와 통합되어 Prometheus, Graphite, New Relic 같은 외부 모니터링 시스템과 연동 가능하다.
    • 특정 메트릭 이름을 지정하면 세부 지표를 확인할 수 있다.
# 전체 메트릭 목록
GET /actuator/metrics
# 특정 메트릭 상세 조회
GET /actuator/metrics/jvm.memory.used
GET /actuator/metrics/http.server.requests

(참고) Prometheus + Grafana

Actuator+Prometheus+Grafana 모니터링 스택으로 부르며 모니터링 방법으로 가장 많이 활용되는 아키텍처
Spring Actuator는 애플리케이션의 헬스 체크와 메트릭 데이터를 노출하고, Prometheus는 이 메트릭을 주기적으로 수집·저장하는 모니터링 서버 역할을 하며, Grafana는 Prometheus에 저장된 데이터를 기반으로 시각화 대시보드를 제공해 실시간 상태를 모니터링할 수 있다.

profile
Backend engineer

0개의 댓글