devFancy BE Developer

실전! 스프링 부트와 JPA 활용2 - API 개발 기본

2023-10-18
devFancy
JPA

이 글의 코드와 정보들은 [실전! 스프링 부트와 JPA 활용 2] 강의를 들으며 정리한 내용을 토대로 작성하였습니다.

API 개발 기본

  • 실무에서는 하나의 도메인에 대한 엔티티를 만들고(ex. 회원), 해당 엔티티를 위한 API가 다양하게 만들어지는데, 한 엔티티에 각각의 API를 위한 모든 요구사항을 담기 어렵다.

  • 그리고 엔티티가 변경되면 API 스펙이 변한다.

  • 결론: 그렇기 때문에 API 기능 요구사항에 맞춰서 엔티티가 아닌 별도의 DTO를 생성하여 파라미터로 받아야 한다.

안 좋은 예시

참고로, 원래라면 DTO도 클래스로 생성해야 하지만, 여기서는 이해를 우선시하기 때문에 Controller 클래스안에 DTO에 대한 코드도 담겨있다는 걸 참고하길 바란다.

V1의 경우, 엔티티를 생성해서 회원에 대한 등록 API를 기능 구현한 예시다.

package jpabook.jpashop.api;

import jpabook.jpashop.domain.Member;
import jpabook.jpashop.service.MemberService;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequiredArgsConstructor
    public class MemberApiController {

    private final MemberService memberService;
    
    // 실제 애플리케이션에서는 엔티티를 직접 반환해서는 안된다.
    @PostMapping("/api/v1/members")
    public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member) {
        Long id = memberService.join(member);
        return new CreateMemberResponse(id);
    }

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

    }

    @Data
    static class CreateMemberResponse {
        private Long id;

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

이런 경우, 위에서 말했던 것 처럼 나중에 엔티티가 변경되면 API 스펙도 같이 변해야 한다.

그래서 해결책으로 엔티티를 대체하는 DTO를 생성하여 API 기능을 구현하면 된다.

좋은 예시

package jpabook.jpashop.api;

import jpabook.jpashop.domain.Member;
import jpabook.jpashop.service.MemberService;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequiredArgsConstructor
    public class MemberApiController {

    private final MemberService memberService;
    
    // 엔티티 노출하거나 받지 말고 api 스펙에 맞는 DTO를 만들고, 그걸 활용해야 한다.
    @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 CreateMemberRequest {
        @NotEmpty
        private String name;

    }

    @Data
    static class CreateMemberResponse {
        private Long id;

        public CreateMemberResponse(Long id) {
            this.id = id;
        }
    }
}
  • 이를 통해 다음과 같은 장점을 가질 수 있다.

    • 이를 통해 엔티티와 프리젠테이션(Controller 클래스와 같은) 계층을 위한 로직을 분리할 수 있다.

    • 엔티티와 API 스펙을 명확하게 분리할 수 있다.

    • 엔티티가 변해도 API 스펙이 변하지 않는다.

참고: 엔티티를 외부에 노출해선 안된다.

실무에서는 기능에 따라 필요한 API가 증가하게 된다. API 마다 필요한 데이터가 다르기 때문에 API 스펙에 맞는 별도의 DTO를 노출해야 한다.

Review

Reference


Comments

Index