[JPA] API 개발 - 기본

이준영·2022년 11월 2일
0

실무에서는 기능개발문제를 넘어서 기술 개발문제가 더 중요해진다.
처리할 데이터가 많은 실무에서는 성능, 유지 보수를 만족하는 기술 개발을 하는 것이 어렵다!!

기능개발과 기술개발

이번 강의에서는 API 개발성능 최적화 이 두가지에 초점을 맞춘 강의가 될 것 같다.

  • REST API 개발
  • 성능 최적화

먼저 API에 대해 알아보자

API 란?

간단하게 말하자면 API는 두 소프트웨어가 서로 통신할 수 있게 하는 메커니즘이다.
API를 일종의 함수라고 보면 서로 다른 두 소프트웨어(클라이언트, 서버)가 통신을 하기 위해 API라는 함수를 이용하는 것이다!

지금까지는 템플릿 엔진(thymleaf)를 이용하여 서버에서 이것 저것 렌더링 해주는 방식을 사용했다.
API를 이용하면 클라이언트가 서버에 Request를 보내면 서버가 클라이언트에 Response 해주는 방식이 사용된다.

"한마디로 통신을 위한 함수라고 정리할 수 있다!!"

API에 대한 자세한 설명은 여기에 들어가보자

JPA는 엔티티 개념이 있기 때문에 지금까지 해왔던 API와 다른점이 있다고 하니 주의하며 공부하자!!

REST API 란?

API의 작동 방식 중 하나로
클라이언트와 서버는 HTTP를 사용하여 데이터를 교환하는 방식을 사용한다.
HTTP의 편리성을 최대한 이용한 API 방식이라고 보면 될 것 같다.
REST는 GET, PUT, DELETE 등의 함수 집합을 정의한다.

"한마디로 서버와 클라이언트가 GET, PUT, DELETE등을 사용하여 HTTP로 통신하는 방식이라고 정리할 수 있다!!"

REST API에 대한 자세한 설명은 여기에 들어가보자


API 개발

API와 템플릿 엔진을 이용한 VIEW는 공통적으로 처리하는게 서로 다르기 때문에
API를 개발할 때는 템플릿 엔진을 사용하여 렌더링하는 컨트롤러와 API 스타일 컨트롤러를 분리하는게 좋다.

  • API Controller 생성
@RestController	// @Controller + @ResponseBody
@RequiredArgsConstructor
public class MemberApiController {
	
    private final MemberService memberService;
    
    /** 여기에 클래스 및 함수 작성 **/
    
}

@RestController@Controller@ResponseBody의 기능을 둘다 가지고 있는 친구이다


회원 등록 API

version_1(v1) 과 version_2(v2) 두가지 방법을 사용해서 회원 등록을 처리하는 API를 구현할 것이다
v1의 방식으로 하면 안되니까 v2까지 만들었겠지??

  • version_1 : 엔티티를 파라미터로 받는 방식
    private final MemberService memberService;
    /**
     *   회원 등록 API v1 : 엔티티를 파라미터로 받아서 사용
     *   결론 : 이렇게 쓰면안됨
     */
    @PostMapping("/api/v1/members")

    public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member) {
        Long id = memberService.join(member);
        return new CreateMemberResponse(id);
    }
    
    @Data
    static class CreateMemberRequest {
        private String name;
    }
  1. 회원 등록 요청과 응답을 위한 CreateMemberRequest() & CreateMemberResponse() 클래스를 만들고 함수를 정의

  2. @RequestBody : json 데이터를 처리해주는 친구. json으로 온 body를 그대로 매핑해서 넣어준다.

  3. @Valid : 데이터가 유효한지 검증해주는 친구. javax.validation을 검증한다

v1은 엔티티(Member)를 파라미터로 받아서 사용한 경우이다.

이게 왜 문제냐?!

API에 맞추기 위해 엔티티를 수정하면 API 스펙 자체가 변한다.
엔티티는 여러곳에서 쓰이기 때문에 바뀔 확률이 높은데 
API가 엔티티를 그대로 받아서 사용하면 엔티티가 바뀔 때 마다 API 스펙이 바뀌어 버려서 오류가난다.

API 스펙이 바뀌어?? 무슨소리지

예를 들어줄게
v1 버전을 통해 회원 가입을 할 때 이름에 아무 값도 안넣고(NULL) 회원가입을 하면 성공적으로 회원가입이 완료된다.
이름이 null인게 마음에 안들어!
회원가입시 이름 입력은 필수로 하기 위해서 Member 엔티티에 들어가서 name 값에 '@NotEmpty'를 붙여주면  
'@Valid'가 검증을 해주기 때문에 이름이 null이면 회원 가입 에러!!

이러면 API를 위해서 엔티티가 수정 돼버린상황..

어떤 API는 @NonEmpty가 필요할수도 있지만 다른 API는 필요하지 않을수도 있다

그럼 어떡해??

API 스펙을 위한 별도의 DTO(Data Transfer Object)를 만들어주자!!!
--> API 스펙에 맞춰 별도의 파라미터를 생성해야한다.

이 방식을 사용한게 바로 v2.
DTO를 만들기 위해 @Data를 붙여주고 API 스펙에 맞는 클래스 생성!!

  • version_2 : 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);
    }

    // DTO 생성
    @Data
    static class CreateMemberRequest {
        private String name;
    }

    @Data
    static class CreateMemberResponse {
        public CreateMemberResponse(Long id) {
            this.id = id;
        }

        private Long id;
    }



회원 조회 API

회원 등록과 마찬가지로 version_1(v1) 과 version_2(v2) 두가지 방법을 사용해서 회원 조회를 처리하는 API를 구현할 것이다
v1의 방식으로 하면 안되니까 v2까지 만들었다.

  • version_1 : 엔티티가 노출되는 방식
	@GetMapping("/api/v1/members")
	public List<Member> MemberV1() { return memberService.findMembers(); }

와.. 간단 그 자체..!!
이지만, 회원 등록에서와 같이 회원 조회에서도 엔티티를 직접 쓰면 안된다.

엔티티를 외부에 노출해선 안된다고 표현하는데, 회원 정보를 가져오는데 엔티티를 직접 노출해서 가져오면 문제가 생긴다.

어떤문제??

관련된 모든 정보를 가져오는 문제!! join된 모든 정보를 가져와버린다

이게 왜 문제야??

예를 들어줄게
회원 정보만 조회 할건데 엔티티와 관련된 모든 정보를 가져와
쓸데없이 많은 데이터를 가져오는 상황이 발생하는거지
또 노출되면 안되는 정보까지 가져와버린다(패스워드, 개인정보, 등...)

회원 등록때와 마찬가지로 이걸 막기 위해서 Member 엔티티에서 join된 애한테 @JsonIgnore를 붙여주면 해결 되긴해
근데 이러면 위에서와 마찬가지로 엔티티를 수정하는 것이기 때문에 스펙 자체가 바뀌게된다

이러면 다른 API를 만들때 문제가 된다~~ 이말이지.
또 @JsonIgnore은 화면에 표시하기위한 로직이잖아. 이런 로직은 지양한다.

그럼 어떡해??

API 스펙을 위한 별도의 DTO(Data Transfer Object)를 만들어주자!!!
--> API 스펙에 맞춰 별도의 파라미터를 생성해야한다.

이 방식을 사용한게 바로 v2.
DTO를 만들기 위해 @Data를 붙여주고 API 스펙에 맞는 클래스 생성!!

  • version_2 : API스펙을 위한 DTO 생성
	@GetMapping("/api/v2/members")
    public Result memberV2() {
        List<Member> findMembers = memberService.findMembers();
        List<MemberDto> collect = findMembers.stream()
                .map(m -> new MemberDto(m.getName()))
                .collect(Collectors.toList());

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

    }

    @Data
    @AllArgsConstructor
    static class Result<T> {
        private int count;
        private T data;
    }

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

회원 등록과 마찬가지로 DTO를 만들어서 엔티티가 노출되지 않게 한다.

  • 수정 API

저번 포스트에서 수정의 방법에는 변경 감지병합(merge)이 있다는 것을 공부했고 우리는 변경 감지를 사용하기로 했다!!

@PutMapping("/api/v2/members/{id}")
    public UpdateMemberResponse updateMemberV2(
            @PathVariable("id") Long id,    // url에  {id}부분 값을 넘겨주는 역할
            @RequestBody @Valid UpdateMemberRequest request) {

        // 수정할 떈 변경감지!
        // 커맨드와 쿼리를 분리해서 사용
        memberService.update(id, request.getName());
        Member findMember = memberService.findOne(id);
        return new UpdateMemberResponse(findMember.getId(), findMember.getName());

    }

    @Data
    static class UpdateMemberRequest {
        private String name;
    }

    @Data
    @AllArgsConstructor
    static class UpdateMemberResponse {
        private Long id;
        private String name;
    }

수정도 등록,조회와 마찬가지로 DTO를 생성해서 파라미터에 매핑해야 한다.
한가지 더 짚고 넘어갈 만한 것은 url에 {id}를 넣고 @PathVariable("id")을 이용해 id값을 넘겨받는것 정도??

  • 참고
    REST 함수에서 수정의 방법에는 POST / PUT / PATCH 가 있는데
    POST, PATCH는 부분 수정.
    PUT은 전체 수정이다.

결론

API를 만들때 엔티티를 파라미터로 받지도 말고 엔티티를 외부에 노출해서도 안된다

API에서 요청/응답 할 때 절대 엔티티를 사용하지 않고 DTO를 생성해서 요청/응답 해야한다.

API를 만들때는 엔티티를 DTO 로 변환하는 작업을 추가하자
➡️ 엔티티가 변경돼도 API 스펙이 변하지 않는다
➡️ 한번 감싸서 반환했기 때문에 유연성이 좋아진다

profile
화이팅!

0개의 댓글