프로그래밍/spring boot

[스프링부트] 실전! 스프링 부트와 JPA 활용2 #1 API 개발(3) 회원 조회

aSpring 2023. 11. 14. 20:30
728x90
728x90
※ 본 포스팅은 김영한 강사님의 인프런 '실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화' 강의를 들으며 작성한 수강일지 입니다.

 

 

 

 

| API 개발 기본

1. 회원 등록 API
2. 회원 수정 API
3. 회원 조회 API

 

3. 회원 조회 API

조회만 하는 것이기 때문에 jpa.hibernate.ddl-auto: none으로 변경해 한 번 데이터를 넣어두면 그 DB에 있는 데이터를 그대로 쓸 수 있음

application.yml

 

회원조회 V1 : 응답 값으로 엔티티를 직접 외부에 노출

// 회원 목록 조회
@GetMapping("/api/v1/members")
public List<Member> membersV1() {
    return memberService.findMembers();
}

 

-> 순수하게 회원 정보만 얻고싶은 것인데, 주문 정보가 있다면 그것까지 모두 노출되게 됨

-> @JsonIgnore 써주기

-> orders 정보는 제외하고 노출되는 모습(상황에 따라 entity 안에 JsonIgnore를 써주기 곤란함. 다른 조치 필요)

 

조회 V1: 응답 값으로 엔티티를 직접 외부에 노출

  • 문제점
    • 엔티티에 프레젠테이션 계층을 위한 로직이 추가됨
    • 기본적으로 엔티티의 모든 값이 노출됨
    • 응답 스펙을 맞추기 위해 로직이 추가됨(@JsonIgnore, 별도의 뷰 로직 등등)
    • 실무에서는 같은 에티티에 대해 API가 용도에 따라 다양하게 만들어지는데, 한 엔티티에 각각의 API를 위한 프레젠테이션 응답 로직을 담기는 어려움
    • 엔티티가 변경되면 API 스펙이 변함
    • 추가로 컬렉션을 직접 반환하면 향후 API 스펙을 변경하기 어려움(별도의 Result 클래스 생성으로 해결).
  • 결론
    • API 응답 스펙에 맞추어 별도의 DTO를 반환한다.
참고: 엔티티를 외부에 노출하지 마세요!
실무에서는 member 엔티티의 데이터가 필요한 API가 계속 증가하게 됨. 어떤 API는 name 필드가 필요하지만, 어떤 API는 name 필드가 필요없을 수 있음. 결론적으로 엔티티 대신에 API 스펙에 맞는 별도의 DTO를 노출해야 함

 

 

회원조회 V2: 응답 값으로 엔티티가 아닌 별도의 DTO 사용

 

  • 엔티티를 DTO로 변환해서 반환
  • 엔티티가 변해도 API 스펙이 변경되지 않음
  • 추가로 Result 클래스로 컬렉션을 감싸서 향후 필요한 필드를 추가할 수 있음

 

아래처럼 결과값이 나오게 되는데 count를 넣어달라는 등.. -> JSON 스펙이 깨져버림

바로 array로 반환하면 스펙이 고정되어 버림

[
    {
        "id": 1,
        "name": "new-hello",
        "address": null
    },
    {
        "id": 2,
        "name": "member1",
        "address": {
            "city": "서울",
            "street": "test",
            "zipcode": "1111"
        }
    },
    {
        "id": 3,
        "name": "member2",
        "address": {
            "city": "부산",
            "street": "부산거리",
            "zipcode": "54597"
        }
    },
    {
        "id": 4,
        "name": "test1",
        "address": {
            "city": "test city",
            "street": "test street",
            "zipcode": "559755"
        }
    }
]

-> 이럴 때는 아래처럼!! array를 바로 반환한는게 아니라 아래같은 방식으로

{
	"count": 4,
    "data": [
                [
            {
                "id": 1,
                "name": "new-hello",
                "address": null
            },
            {
                "id": 2,
                "name": "member1",
                "address": {
                    "city": "서울",
                    "street": "test",
                    "zipcode": "1111"
                }
            },
            {
                "id": 3,
                "name": "member2",
                "address": {
                    "city": "부산",
                    "street": "부산거리",
                    "zipcode": "54597"
                }
            },
            {
                "id": 4,
                "name": "test1",
                "address": {
                    "city": "test city",
                    "street": "test street",
                    "zipcode": "559755"
                }
            }
        ]
    ]
}

-> Result로 한번 감싸기

@GetMapping("/api/v2/members")
    public Result memberV2() {
        List<Member> findMembers = memberService.findMembers(); // MemberDTO로 변환 필요
        List<MemberDto> collect = findMembers.stream()
                .map(m -> new MemberDto(m.getName()))
                .collect(Collectors.toList());

        return new Result(collect);
    }

    @Data
    @AllArgsConstructor
    static class Result<T> { // Result 라는 껍데기로 한번 감싸주기
        private T data;
    }

    @Data
    @AllArgsConstructor
    static class MemberDto {
        private String name;
    }

{
    "name": "new-hello"
},
// memberDto로 감싼 것

@GetMapping("/api/v2/members")
public Result memberV2() {
    List<Member> findMembers = memberService.findMembers(); // MemberDTO로 변환 필요
    List<MemberDto> collect = findMembers.stream()
            .map(m -> new MemberDto(m.getName()))
            .collect(Collectors.toList());

    return new Result(collect.size(), collect);
}

@Data
@AllArgsConstructor
static class Result<T> { // Result 라는 껍데기로 한번 감싸주기
    private int count;
    private T data;
}

한 번 감싸주면 다른 데이터들도 함께넘겨줄 수 있음

728x90
728x90