[spring] API 개발 기본

vector13·2022년 4월 4일
0

spring

목록 보기
21/25

API 툴 - POSTMAN 설치

회원 등록 API


패키지 분리 api >MemberApiController생성
@Controller @ResponseBody 합친 @RestController 사용

엔티티 받고
api통신 json으로 온 데이터를 멤버로 바로 바꿔준다. Mapping

package jpabook.jpashop.api;


import jpabook.jpashop.domain.Member;
import jpabook.jpashop.service.MemberService;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@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 CreateMemberResponse{
        private Long id;

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

PostMan에서


해서 Send 버튼을 누르면 Method Not allowed 뜬다
get으로 보내서 그렇다.

Post로 바꾸고 다시


잘 저장되고 1 이 되돌아온다

아름을 빼고 저장해도 그대로 null이 들어가는데, 엔티티 제약조건을 넣지 않아서 그렇다.
Member.java 에 name 필드에 @NotEmpty 넣어주면된다.

null일 경우 안들어간다.
@NotEmpty (javax validation) 가 검증한다. @Valid Member member
같은 이름 넣으려하면 이미 존재하는 회원으로 에러뜬다.

화면에서 들어오는 Controller (프레젠테이션 계층)의 검증로직이 api에 들어가있는 것이다.
화면 validation위한 로직 들어간 것이다. 어떤 화면에선 이거 필요할수없을 수도있다.

만일 username으로 Member의 name필드를 바꾸면 (Member엔티티 명을 수정)
api스펙 자체가 username으로 바뀐다.

문제는 엔티티를 손대서 api 스펙이 바뀐게 문제
api는 여러곳에서 호출하는데 지금 일대일로 매핑되어있음

결론은 api스펙을 위한 별도의 DTO를 만들어야한다. 외부에서 바로 바인딩해서 하면 나중에 큰 장애가 생긴다.

API 요청 스펙에 맞추어 별도의 DTO를 파라미터로 받는다

  • 문제점
    엔티티에 프레젠테이션 계층을 위한 로직이 추가된다.
    엔티티에 API 검증을 위한 로직이 들어간다. (@NotEmpty 등등)
    실무에서는 회원 엔티티를 위한 API가 다양하게 만들어지는데, 한 엔티티에 각각의 API를 위한모든 요청 요구사항을 담기는 어렵다.
    엔티티가 변경되면 API 스펙이 변한다.
  • 결론
    API 요청 스펙에 맞추어 별도의 DTO를 파라미터로 받는다.

그러므로 기존코드보다 response와 request각자 객체를 만드는 방식으로 사용

@PostMapping("api/v2/members")
    public CreateMemberResponse saveMember2(@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{
        private String name;
    }

send하면 제대로 insert되고 id도 반환된다.

만일 엔티티 에서 값이 변환된다면 변환되는 시점에 컴파일 오류가 된다.
그러면 api영향 없이 중간에서 파라미터랑 엔티티랑 매핑해서 확인되어서 서비스 안정적으로 볼 수있음

validation도 MemberApiController에 넣으면된다.

Member에 @NotEmpty넣으면 어떤 api에서는 낫엠티인데 어디서는 null 가능일수도있어서
api스펙에 fit하게 맞춰서 하기
엔티티 외부 노출하지 않고 api스펙에 맞는 객체(DTO)를 만드는 것이 정석이다.

엔티티와 프레젠테이션 계층을 위한 로직을 분리할 수 있다.
엔티티와 API 스펙을 명확하게 분리할 수 있다.
엔티티가 변해도 API 스펙이 변하지 않는다.

회원 수정 API


회원 수정도 DTO를 요청 파라미터에 매핑

수정 API MemberApiController 클래스에

   /**
     * 수정 API
     */
    @PutMapping("api/v2/members/{id}")
    public UpdateMemberResponse updateMemberV2(
            @PathVariable("id") Long 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;
    }

MemberService 클래스에 넣어주고

@Transactional
    public void update(Long id, String name) {
        Member member = memberRepository.findOne(id);
        member.setName(name);
    }

Post로 hello 넣어주고

put으로 new-hello 바꿔보기

회원 조회 API


auto ddl을 create 에서 none으로 변경

조회 기능 할건데 반복적으로 하면 귀찮으니까

암꺼나 테스트 데이터 넣어놓고

가장 안좋은 버전으로 코드를 짜고 실행해보면

 @GetMapping("/api/v1/members")
    public List<Member> membersV1() {
        return memberService.findMembers();     //가장 안좋은 버전 
    }

잘 출력된다.

근데 이건 엔티티에 있는 정보 모두 노출시키는 거다.
회원 관련 조회 api는 하나가 아닐거고, 그때마다 @JsonIgnore 해서 케이스별로 엔티티에 로직을 녹이기 시작하면 노답

그리고 엔티티에 프레젠테이션 계층을 위한 로직이 추가되었음
api스펙이랑 화면관련 기능이 엔티티에 들어온거
의존관계가 엔티티로 들어와야하는데 나가는 양방향 의존관계가 되어서 애플리케이션 수정이 어려워졌음

이 v1방식으로는 모든 케이스를 받을 수 없음

엔티티 직접 반환은 안된다.

  • 문제점
    엔티티에 프레젠테이션 계층을 위한 로직이 추가된다.
    기본적으로 엔티티의 모든 값이 노출된다.
    응답 스펙을 맞추기 위해 로직이 추가된다. (@JsonIgnore, 별도의 뷰 로직 등등)
    실무에서는 같은 엔티티에 대해 API가 용도에 따라 다양하게 만들어지는데, 한 엔티티에 각각의 API를 위한 프레젠테이션 응답 로직을 담기는 어렵다.
    엔티티가 변경되면 API 스펙이 변한다.
    추가로 컬렉션을 직접 반환하면 항후 API 스펙을 변경하기 어렵다.(별도의 Result 클래스 생성으로 해결)

  • 결론

API 응답 스펙에 맞추어 별도의 DTO를 반환한다.

그러므로 v2로 응답 값으로 엔티티가 아닌 별도의 DTO 사용
Result 로 껍데기 감싸고 데이터 필드값은 list가 나간다.
list 바로 나가면 json 배열 타입으로 나가서 유연성이 확 떨어진다.

/**
     * 조회 V2: 응답 값으로 엔티티가 아닌 별도의 DTO를 반환한다.
     */
    @GetMapping("/api/v2/memebrs")
    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);

    }

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

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

get요청해보면
data는 해서 배열이 들어갔고, 여기에는 이름만 있음
이것이 멤버 DTO감싼것임


엔티티를 DTO로 변환해서 반환한다.
엔티티가 변해도 API 스펙이 변경되지 않는다. -> 딱 노출할거만 스펙에 노출힌다,

api스펙이 dto와 1대일 매핑!

추가로 Result 클래스로 컬렉션을 감싸서 향후 필요한 필드를 추가할 수 있다.

절대 엔티티 외부로 반환X

무조건 DTO로 바꾸기!

profile
HelloWorld! 같은 실수를 반복하지 말기위해 적어두자..

0개의 댓글

관련 채용 정보