[Spring] 스프링 부트 값 검증(Validation) 가이드

이 글은 [스프링 부트 3 백엔드 개발자 되기(자바 편)(2판)]에서 발췌했습니다.
골든래빗 출판사

값 검증(Validation)은 사용자가 요청을 보냈을 때 올바른 값인지 유효성 검사를 하는 과정입니다. 예를 들어 서버에서 로직을 처리하기 전에 사용자가 잘못된 데이터를 보냈다고 해봅시다. 이럴 때는 서버에서 로직을 처리하기 전에 사용자에게 ‘입력한 데이터가 올바르지 않다’라는 에러 메시지를 보여주면 됩니다. 이렇게 하면 서버에서 서비스 로직을 실행하지 않으니 조금 더 시스템을 안정적으로 관리할 수 있죠.

 

[Spring] 스프링 부트 값 검증 가이드

《스프링 부트 3 백엔드 개발자 되기 (자바편)》에서 작성한 코드를 바탕으로 값 검증에 대해 이야기해보겠습니다. AddArticleRequest.java 파일을 열어주세요.

 

@NoArgsConstructor
@AllArgsConstructor
@Getter
public class AddArticleRequest {
    private String title;
    private String content;
   
    public Article toEntity(String author) {
        return Article.builder()
                .title(title)
                .content(content)
                .build();
    }
}

 

이 코드는 블로그 글 추가 요청을 받기 위한 DTO입니다. 코드에서 보듯 이 DTO는 String형 title, content를 가지고 있습니다. 그런데 만약 누군가 이렇게 요청을 보내면 어떻게 처리해야 할까요?

 

▼ content가 없는 블로그 글 추가 요청

{
  "title": " "
}

 

이런 경우 title에는 “제목”이, content에는 null이 들어올 것입니다. 그런데 Article 엔티티에서는 content의 속성이 nullable = false로 정의되어 있네요. 그러면 null을 저장하는 순간 예외가 발생하며 서버에 문제가 생길 것입니다. 이런 경우 서버단이 아니라 요청단에서 값 검증을 하여 처음부터 content값이 없으면 사용자에게 알려주는 등의 방법을 사용하면 이런 상황을 예방할 수 있습니다. 그 방법을 알아봅시다.

스프링에서는 자바 빈 밸리데이션(Java Bean Validation)이라는 API를 제공합니다. 이 API를 사용하면 애너테이션 기반으로 다양한 검증 규칙을 간편하게 사용할 수 있고 입력 데이터의 유효성을 검사할 수 있습니다. 자주 사용하는 몇가지 애너테이션을 살펴보겠습니다. 애너테이션 이름을 보면 알겠지만 이름에 ‘검증 규칙 목적’이 명확하게 보이므로 직관적으로 이해할 수 있을 겁니다.

 

▼ 자주 사용하는 자바 빈 밸리데이션 예

/**
* 문자열을 다룰 때 사용
*/
@NotNull // null 허용하지 않음
@NotEmpty // null, 빈 문자열(공백) 또는 공백만으로 채워진 문자열 허용하지 않음
@NotBlank // null, 빈 문자열(공백) 허용하지 않음
@Size(min=?, max=?) // 최소 길이, 최대 길이 제한
@Null // null만 가능

/**
* 숫자를 다룰 때 사용
*/
@Positive // 양수만 허용
@PositiveOrZero // 양수와 0만 허용
@Negative // 음수만 허용
@NegativeOrZero // 음수와 0만 허용
@Min(?) // 최솟값 제한
@Max(?) // 최댓값 제한

/**
* 정규식 관련
*/
@Email // 이메일 형식만 허용
@Pattern(regexp="?") // 직접 작성한 정규식에 맞는 문자열만 허용

 

값 검증은 어느 계층에서 해도 상관없습니다. 프레젠테이션 계층에서 컨트롤러에 요청이 오는 순간 검증할 수도 있고, 퍼시스턴스 계층에서 엔티티에 적용할 수도 있죠. 부록에서는 프레젠테이션 계층에서 검증하는 과정을 소개합니다. 보통은 프레젠테이션 계층에 검증 코드를 작성해야 불필요한 서비스 로직을 실행하지 않을 수 있고, 또, 사용자 요청마다 세부 조건을 적용할 수 있기 때문입니다. 그럼 실습을 통해서 실제로 값 검증을 하는 방법을 알아보겠습니다.

 

💡 상황에 따라 중복 로직이 너무 많이 생기거나 검증 로직을 통일하기 어려우면 퍼시스턴스 계층인 엔티티에 검증 코드를 작성하기도 합니다.

 

01단계

build.gradle 파일을 열어 의존성을 추가해주세요.

 

dependencies {
  ... 생략 ...
  implementation 'org.springframework.boot:spring-boot-starter-validation'
  testImplementation 'com.github.javafaker:javafaker:1.0.2'
  ... 생략 ...

 

Faker는 테스트를 진행할 때 가짜 데이터를 생성해주는 오픈소스 라이브러리입니다. 쉽게 말해이 라이브러리를 사용하면 이름, 주소, 이메일 같은 가짜 정보를 쉽게 생성할 수 있습니다. 예를 들어 다음과 같이 가짜 정보를 생성할 수 있습니다.

 

▼ Faker로 가짜 정보를 만든 예

Faker faker = new Faker(new Locale("ko")); // Local을 넣지 않으면 영어로 생성

String name = faker.address().fullAddress(); // 85877 구로읍, 부천구, 부산
String firstName = faker.name.name(); // 홍 길동
String lastName = faker.food.fruit(); // Melon

 

이제 사용자가 입력해야 할 값에 어떤 유효성이 필요할지 정의해보겠습니다. 필자의 경우 블로그글을 생성할 때는 이런 유효성이 있어야 한다고 생각합니다. 이 유효성을 바탕으로 코드를 작성해보겠습니다.

 

 

02단계

검증 로직을 작성하기 전에 검증 로직을 테스트할 테스트 코드부터 작성합시다. BlogApiControllerTest.java 파일을 열어 다음과 같이 코드를 작성하세요.

 

@DisplayName("addArticle: 아티클 추가에 성공한다.")
@Test
public void addArticle() throws Exception {
  // given
  final String url = "/api/articles";
  final String title = "title";
  final String content = "content";
  final AddArticleRequest userRequest = new AddArticleRequest(title, content);
  
  final String requestBody = objectMapper.writeValueAsString(userRequest);
  
  Principal principal = Mockito.mock(Principal.class);
  Mockito.when(principal.getName()).thenReturn("username");

  // when
  ResultActions result = mockMvc.perform(post(url)
        .contentType(MediaType.APPLICATION_JSON_VALUE)
        .principal(principal)
        .content(requestBody));

  // then
  result.andExpect(status().isCreated());
}

@DisplayName("addArticle: 아티클 추가할 때 title이 10자를 넘으면 실패한다.")
@Test
public void addArticleNullValidation() throws Exception {
  // given
  Faker faker = new Faker();
  
  final String url = "/api/articles";
  final String title = null;
  final String content = "content";
  final AddArticleRequest userRequest = new AddArticleRequest(title, content);
   
  final String requestBody = objectMapper.writeValueAsString(userRequest);
  
  Principal principal = Mockito.mock(Principal.class);
  Mockito.when(principal.getName()).thenReturn("username");

  // when
  ResultActions result = mockMvc.perform(post(url)
        .contentType(MediaType.APPLICATION_JSON_VALUE)
        .principal(principal)
        .content(requestBody));

  // then
  result.andExpect(status().isBadRequest());
}

 

값의 유효성을 검증하기 위한 2개의 테스트 케이스를 새로 작성했습니다. 각 테스트 케이스는 다음과 같은 given-when-then 패턴을 가집니다.

 

▼ addArticleNullValidation()

 

▼ addArticleSizeValidation()

 

테스트를 실행하면 이번에 추가한 2개의 테스트는 모두 실패할 것입니다. 왜냐하면 아직 유효값 검증 로직을 작성하지 않았기 때문이죠.

 

 

03단계

블로그 글 추가 요청을 받을 때 사용하는 DTO인 AddArticleRequest.java 파일을 열어 값 검증 애너테이션을 추가하세요.

 

@NoArgsConstructor
@AllArgsConstructor
@Getter
public class AddArticleRequest {

    @NotNull
    @Size(min = 1, max = 10)
    private String title;

    @NotNull
    private String content;

    public Article toEntity(String author) {
        return Article.builder()
                .title(title)
                .content(content)
                .author(author)
                .build();
    }
}

 

title에는 Null을 허용하지 않는 @NotNull과 1자 이상 10자 이하의 조건을 설정하는 @Size 애너테이션을, content에는 @NotNull만 추가했습니다.

 

04단계

블로그 글 추가 요청을 받는 BlogApiController.java 파일을 열어 코드를 수정해주세요.

 

@PostMapping("/api/articles")
public ResponseEntity<Article> addArticle(@RequestBody @Validated AddArticleRequest request, Principal principal) {
  Article savedArticle = blogService.save(request, principal.getName());
  
  return ResponseEntity.status(HttpStatus.CREATED)
          .body(savedArticle);
}

 

@Validated 애너테이션을 추가하여 메서드에 들어오는 파라미터가 유효한 값인지 검증합니다. 이제 코드 수정이 모두 끝났으니 테스트 코드를 다시 실행해보세요. BlogApiControllerTest.java 파일을 다시 실행하면 다음과 같이 모든 테스트가 잘 실행될 것입니다.

 

 

이런 방식으로 값 검증을 하면 쉽게 서버의 안정성을 챙길 수 있을 것입니다. 꼭 알아두었다가 실무에 잘 활용하기 바랍니다.

신선영

리멤버 백엔드 개발자. 하드 스킬과 소프트 스킬 역량을 강화하고자 부단히 공부하고 글로 남기는 백엔드 개발자입니다. 평일 기준 하루 평균 600뷰의 기술 블로그를 운영하고, 모교 학생을 대상으로 정기 세미나와 멘토링을 진행합니다. 구독자가 1,000명 정도 되는 사이드 프로젝트를 기획하고 개발하고 운영한 경험이 있습니다.

저자 블로그 shinsunyoung.tistory.com
저자 깃허브 github.com/shinsunyoung

 

Leave a Reply

©2020 GoldenRabbit. All rights reserved.
상호명 : 골든래빗 주식회사
(04051) 서울특별시 마포구 양화로 186, 5층 512호, 514호 (동교동, LC타워)
TEL : 0505-398-0505 / FAX : 0505-537-0505
대표이사 : 최현우
사업자등록번호 : 475-87-01581
통신판매업신고 : 2023-서울마포-2391호
master@goldenrabbit.co.kr
개인정보처리방침
배송/반품/환불/교환 안내