본문 바로가기
Back-end/JPA 개념

JPA를 이용한 API 개발 꿀팁 - Annotation

by devraphy 2022. 5. 27.

1. API 컨트롤러를 분리한다.

- API를 제작하기 이전에 기본적인 CRUD기능을 다루는 컨트롤러를 생성했을 것이다.

- 그리고 API를 제작할 때에는 API 컨트롤러를 저장할 패키지를 따로 만들어 사용한다. 

 

 

2. @Valid, @NotEmpty

a) @Valid

- API를 통해서 POST 메서드로 데이터를 DB에 저장하거나 변경할 때에는 @Valid와 @NotEmpty를 사용한다.

- @Valid는 javax 라이브러리의 기능으로 유효성을 검사하는 기능을 갖는다.

 

 

b) @NotEmpty

- @NotEmpty는 @Valid에서 검증하는 조건(= constraints) 중 하나다.

- @NotEmpty가 부착된 Entity의 필드 값을 required로 처리한다.

- 이처럼 반드시 필요한 값이 입력되어야 하는 경우, @Valid와 @NotEmpty를 사용하여 API의 요청을 검증할 수 있다.

API의 메서드에서 @Valid 설정

 

Entity의 @NotEmpty 설정

 

c) 위 API 예시의 문제점

- 위의 예시처럼 Entity 레벨에서 validation을 수행하는 구조는 좋지 않다.

- Entity가 presentation 레이어에 노출되는 구조이기 때문이다. 

- 더불어, 이와 같은 API 구조는 Entity의 내용이 변경되는 경우, API의 스펙 자체가 변경되기 때문이다. 

- 그러므로 API 스펙을 위한 별도의 DTO를 생성하여 사용하는 것이 좋은 구조다.

- 즉, API에서 Entity를 직접적으로 파라미터로 받는 방식을 사용하지 말자. 

 

3.  Validation을 위한 올바른 API 구조 

a) 올바른 API의 구조

- 위에서 설명한 내용을 보완하여 다음과 같이 API 구조를 설계하는 것을 권장한다.

@RestController // ==> @Controller + @ResponseBody 내부 확인하면 두 어노테이션이 존재함
@RequiredArgsConstructor
public class MemberApiController {
    private final MemberService memberService;

    @PostMapping("api/v2/members")
    public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request) {
        Member member = new Member();
        member.setName(request.getName());
        Long id = memberService.join(member);
        return new CreateMemberResponse(id);
    }

    @Data
    static class CreateMemberResponse {
        private Long id;

        public CreateMemberResponse(Long id) {
            this.id = id;
        }
    }

    @Data
    static class CreateMemberRequest {
    	@NotEmpty
        private String name;
    }
}

 

 

b) 올바른 API의 구조적 특징 - Client에게 영향을 끼치지 않는다.

- 이와 같은 API 구조는 Entity의 필드명이 변경되어도 Client 측면(= API 호출자)에서 변경사항이 없다는 것이다.

- Entity의 필드명이 변경되더라도 Getter의 명칭만 변경하면 되기에 백엔드 측에서 코드를 수정하면 된다. 

- 즉, Client는 기존에 사용하던 방식 그대로 API를 호출하면 된다.   

 

 

c) 올바른 API의 구조적 특징 - Validation을 Entity에서 수행하지 않는다.

- Validation을 Entity 레벨이 아닌 DTO에서 수행하도록 설계하였다.

- 이와 같은 설계를 하는 이유는, 특정 API에서는 특정 필드에 대한 Validation이 필요할 수도 없을 수도 있기 때문이다.

 

- 위의 코드에서 CreateMemberRequest 클래스를 확인해보면 @NotEmpty가 있는 것을 확인할 수 있다.

- 이 validation은 saveMemberV2() 메서드에서 매개변수에 @Valid를 부착함으로써 활성화된다.  

- 그러므로 기존에 Entity의 필드에 부착되었던 @NotEmpty는 없애도 된다.

 

- 이처럼 Validation을 Entity 레벨에서 수행하지 않고, 직접적인 Entity 사용 대신 DTO를 사용하도록 설계하여

   Entity가 변경되어도 API의 스펙에 영향을 끼치지 않는 구조가 좋은 API의 구조적 특징이다.

 

 

d) 올바른 API의 구조적 특징 - DTO를 사용한다.

- 위의 코드에서는 DTO 역할을 하는 클래스를 static 클래스로 만들어 사용했다.

- 그러나 이들 또한 개별적인 클래스 파일로 분리하여 사용하는 것을 권장한다. 

 

- 이처럼 DTO를 사용하는 이유는 API의 스펙을 쉽게 파악할 수 있고 유지보수에 용이하기 때문이다.

- API 메서드의 DTO를 살펴보면, 해당 API에서 어떤 데이터를 필요로 또는 필수로 하는지 쉽게 확인할 수 있다. 

 

3. 주요 Annotation 정리 

@RestController

- RestAPI 클래스를 작성할 때 사용하는 가장 기본적인 어노테이션

- @RestController는 내부에 @Controller와 @ResponseBody를 포함한다.

- 그러므로 @RestController 대신 @Controller와 @ResponseBody를 부착하면 동일한 역할을 한다.

- 다만, @RestController를 사용하는 것이 Semantic적으로 해당 클래스의 역할이 RestAPI라는 것을 쉽게 이해할 수 있다. 

 

 

@Data

- DTO 클래스 생성 시 사용하는 어노테이션

- @Getter, @Setter, @RequiredArgsConstructor, @ToString, @EqualsAndHashCode가 포함되어 있다.

 

 

@AllArgsConstructor

- 클래스 내부의 모든 필드를 이용하여 생성자를 만드는 어노테이션

 

 

@RequiredArgsConstructor

- final이 붙은 필드를 이용하여 생성자를 만드는 어노테이션

 

 

@Valid 

- 객체 또는 객체의 특정 필드에 대한 유효성을 검증하는 어노테이션

- javax.validation 라이브러리에 속해있는 기능이다.

- 특정 객체를 타고 들어가서 내부의 필드 값을 검증하는 방식으로 Cascading 기능을 가진다. 

- 메서드의 매개변수가 객체인 경우에 매개변수에 부착하여 사용한다.

 

 

@NotEmpty

- @NotEmpty는 @Valid와 함께 쓰이는 대표적인 어노테이션

- 객체의 필드에 부착하여 사용하며, 해당 필드 값을 required로 처리하는 기능이다.

- 어떤 메서드의 매개변수로 넘어오는 객체에 @Valid를 부착하면, 해당 객체의 필드를 확인한다.

- 해당 객체의 필드에 @NotEmpty가 부착되어 있다면, 유효성 기준으로 해당 필드 값의 유무를 판단한다.  

 

 

@RequestBody

- API 메서드의 매개변수에 부착되는 어노테이션

- HTTP request에서 넘어오는 데이터를 받는 매개변수라는 것을 명시하는 기능이다.

- HTTP request를 받는 객체이므로, 해당 매개변수는 일반적으로 DTO가 할당된다.

- 일반적으로 조회(= Get 방식)를 제외한 모든 HTTP 메서드의 API를 구현할 때 사용된다.

 

댓글