Search
Duplicate

API 개발 기본

회원 등록 API

회원 등록 API - V1

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; import javax.validation.constraints.NotEmpty; @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; } } }
Java
복사
엔티티를 RequestBody에 직접 매핑한다.
문제점
엔티티에 프레젠테이션 계층을 위한 로직이 추가된다.
엔티티에 API 검증을 위한 로직이 들어간다.(ex: @NotEmpty ...)
→예를들어, name필드를 어떤 API에서는 NotEmpty하게 사용하고싶어하지만, 또 다른 API에서는Empty역시 허용할 수 있게되면 문제가 될 수 있다.
실무에서는 회원 엔티티를 위한 API가 다양하게 만들어지는데, 한 엔티티에 각각의 API를 위한 모든 요구사항을 넣어줄 순 없다. → 최종접속시간이 추가되고, 어떤 하나의 API때문에 요금정산 수수료 정산율이 추가되고, 하다보면 끝이 없다.
(중요) 엔티티가 변경되면 API의 스펙이 변한다.
→ Member 엔티티의 name 필드가 username이 되는순간 해당 엔티티를 사용하는 API는 모두 변경되야 한다!!

회원 가입 API- V2

→엔티티 대신 DTO를 RequestBody에 매핑했다.
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; import javax.validation.constraints.NotEmpty; @RestController @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; } }
Java
복사
CreateMemberRequest DTO를 Member 엔티티 대신 RequestBody와 매핑한다.
엔티티와 프레젠테이션 계층이 분리되었다.
엔티티와 API 스펙을 명확하게 분리할 수 있다.
엔티티가 변경되어도 API스펙이 변경되지 않는다.

정리

→ 실무에서는 API스펙에 엔티티가 노출되어서는 안된다. 그렇기 때문에 각각에 API에 맞는 DTO를 만들어서 엔티티와 분리시키는게 중요하다.
엔티티를 그대로 쓸 경우의 장점은 아주 조금 간편해진다는 것 뿐이다.

회원 수정 API

회원 수정 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; @RestController @RequiredArgsConstructor public class MemberApiController { private final MemberService memberService; @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; } }
Java
복사
RequestBody에 사용할 DTO들은 해당 컨트롤러에서만 사용한다면 굳이 외부로 빼지않고 INNER CLASS로 만들어도 된다.
update method(Service단)에서 update한 Member를 그대로 반환해줘도 되지만, 커맨드와 쿼리를 철저히 분리한다는 정책이 있기에 update와 같은 커맨드성 로직에서는 가급적이면 비즈니스 로직 종료 후 그대로 끝내거나 식별자(ID)정도만 반환합니다. 그래서 반환받은 측에서 해당 엔티티가 필요하다면 해당 식별자 등을 이용해 다시 Service단에서 조회해서 가져오는 방법을 사용한다.

회원 조회 API

회원 조회 API - V1

@GetMapping("/api/v1/members") public List<Member> membersV1(){ return memberService.findMembers(); }
Java
복사
V1: 응답 값으로 엔티티를 직접 외부에 노출한다.
문제점
1.
엔티티에 프레젠테이션 계층을 위한 로직이 추가된다.(@JsonIgnore)
2.
기본적으로 엔티티의 모든 값이 노출된다.
3.
응답 스펙을 맞추기 위해 로직이 추가된다(@JsonIgnore, 별도의 뷰 로직 등등)
4.
실무에서는 같은 엔티티에 대해 API가 용도에 따라 다양하게 만들어지는데, 한 엔티티에 각각의 API를 위한 프레젠테이션 응답 로직을 담기는 어렵다
5.
엔티티가 변경되면 API스펙이 변한다.(name → username)
6.
추가로 컬렉션을 직접 반환하면 향후 API스펙 변경의 확장성이 몹시 떨어진다. (별도의 Result클래스 생성으로 해결 → ex: Response Object)
결론
→ API응답 스펙에 맞춰 별도의 DTO를 반환한다.

회원 조회 API - V2

@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); } @Data @AllArgsConstructor static class Result<T>{ private int count; private T data; } @Data @AllArgsConstructor static class MemberDto{ private String name; }
Java
복사
엔티티를 DTO로 변환해서 반환한다.
엔티티가 변해도 API스펙이 변경되지 않는다.
추가로 Result class로 컬렉션을 감싸서 향후 필요한 필드를 추가할 수 있다.