728x90
반응형
회원 등록, 수정, 조회 API 개발을 학습해보자
준비사항
포스트맨 설치
API 테스트 툴을 설치한다. (https://www.postman.com/)
포스트맨 설정
REST API 테스트 시 기본적인 설정
회원등록 API
엔티티를 사용하는 방식과 DTO를 사용하는 방식을 각각 구성해보자
엔티티 사용 회원 컨트롤러 구성
package jpabook.jpashop.api;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class MemberApiController {
private final MemberService memberService;
/**
* 등록 V1: 요청 값으로 Member 엔티티를 직접 받는다.
*/
@PostMapping(value="/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;
}
}
}
@RestController = @Controller + @ResponseBody
-@Controller
: 전통적인 스프링 MVC 컨트롤러로 Model 객체를 만들어 데이터를 담고 View를 찾는다.
-@ResponseBody
: 자바 객체를 HTTP 응답 본문의 객체로 변환하여 클라이언트에 전송한다.
-@RequestBody
: HTTP 요청 본문에 담긴 값을 자바 객체로 변환한다.@Data
어노테이션은 Lombok에서 제공하는 것으로@Getter
,@Setter
,@ToString
,@EqualsAndHashCode
,@RequiredArgsConstructor
어노테이션을 모두 포함하는 것이다.
엔티티 사용시 문제점
- 엔티티에 API 검증을 위한 로직이 들어가게 되는데 이때 이 로직이 다른 API에서는 필요하지 않을 수 있어 문제가 발생한다.
- 엔티티가 변경되면 API 스펙이 변경된다.
- 엔티티 내 중요 정보(모든 값)가 노출된다.
- 엔티티 내 변수 중 API 스펙을 확인하지 않고서는 어떤값이 바인딩 되는지 알 수 없다.
- 엔티티 내에 여러 API를 위한 모든 요구사항을 담기 어렵다.
따라서 API 요청 스펙에 맞춰 별도의 DTO를 구성하여 파라미터로 사용해야 한다.
DTO 사용 회원 컨트롤러 구성
package jpabook.jpashop.api;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class MemberApiController {
private final MemberService memberService;
/**
* 등록 V2: 요청 값으로 Member 엔티티 대신에 별도의 DTO를 받는다.
*/
@PostMapping(value="/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 {
private String name;
}
}
CreateMemberRequest
라는 DTO 내부 클래스를 만들어 파라미터로 사용한다.
DTO 사용해야 하는 이유
- 엔티티와 프레젠테이션 계층을 위한 로직을 분리할 수 있다.
- 엔티티와 API 스펙을 명확하게 분리할 수 있어 엔티티가 변해도 API 스펙이 변하지 않는다.
따라서 실무에서는 절대로 엔티티를 API 파라미터로 받거나 외부에 노출해선 안된다.
회원등록 API 동작 결과
회원수정 API
DTO를 사용하여 회원수정 구성
회원 컨트롤러 구성
package jpabook.jpashop.api;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class MemberApiController {
private final MemberService memberService;
/**
* 수정 API
*/
@PutMapping(value="/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;
}
}
@PutMapping
- 새로운 데이터를 생성하거나 기존 데이터를 대체할 때 사용한다.
- 주로 데이터 수정 동작에 많이 사용되며 주로 전체 업데이트 시 사용하고 부분 업데이트는@PatchMapping
이나@PostMapping
을 사용하는 것이 REST 스타일에 맞다.@PostMapping
과@PutMapping
차이
-@PostMapping
은 동일한 요청을 계속해도 결과는 매번 달라지지만@PutMapping
은 동일한 요청을 보낼경우 결과가 매번 같다. 이러한 성질을 멱등성이라 한다.
-@PostMapping
은 주로 데이터 생성 동작에 많이 사용된다.
회원 서비스 구성
package jpabook.jpashop.service;
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
/**
* update one Member
* @param id
* @param name
*/
@Transactional
public void update(Long id, String name) {
Member member = memberRepository.findOne(id);
member.setName(name);
}
}
- 변경감지를 사용하여 회원정보를 수정한다.
회원수정 API 동작 결과
회원조회 API
엔티티를 사용하는 방식과 DTO를 사용하는 방식을 각각 구성해보자
엔티티 사용시 회원 컨트롤러 구성
package jpabook.jpashop.api;
@RestController
@RequiredArgsConstructor
public class MemberApiController {
private final MemberService memberService;
/**
* 조회 V1: 응답 값으로 엔티티를 직접 외부에 노출한다.
*/
@GetMapping(value="/api/v1/members")
public List<Member> membersV1() {
return memberService.findMembers();
}
}
- 조회 결과를 회원 엔티티로 반환한다.
엔티티 사용시 문제점
- 엔티티에 프레젠테이션 계층을 위한 로직이 추가된다.
- 엔티티의 모든 값이 노출된다.
- 응답 스펙을 맞추기 위해 로직이 추가된다. (
@JsonIgnore
, 별도의 뷰 로직..) - 같은 엔티티에 대해 용도에 따른 API가 다양하게 만들어지는데 한 엔티티에 각각의 API를 위한 프레젠테이션 응답 로직을 담기 어렵다.
- 엔티티가 변경되면 API 스펙이 변한다.
- 컬렉션을 직접 반환하면 향후 API 스펙을 변경하기 어렵다. (별도의 Result 클래스를 생성하여 해결)
따라서 API 응답 스펙에 맞춰 별도의 DTO를 구현하여 반환해야 한다.
DTO 사용시 회원 컨트롤러 구성
package jpabook.jpashop.api;
@RestController
@RequiredArgsConstructor
public class MemberApiController {
private final MemberService memberService;
/**
* 조회 V2: 응답 값으로 엔티티가 아닌 별도의 DTO를 반환한다.
*/
@GetMapping(value="/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);
}
@Data
@AllArgsConstructor
static class Result<T> {
private T data;
}
@Data
@AllArgsConstructor
static class MemberDTO {
private String name;
}
}
- 결과를 전달할 클래스
Result<T>
와 DTO를 내부 클래스로 구성한다. - 자바 스트림을 이용하여 조회한 데이터를 Map으로 구성한 후 리스트로 반환한다.
- Result 클래스를 구성한 이유는 결과 데이터 이외에 필요한 필드를 추가하여 사용하기 위함이다.
DTO 사용해야 하는 이유
- 엔티티가 변해도 API 스펙이 변하지 않는다.
- 컬렉션을 Result 클래스로 감싸서 향후 필요한 필드를 추가할 수 있다.
Result 클래스 구성 이유
컬렉션 이외 필요한 필드를 추가하여 사용하기 위함이다.
@GetMapping(value="/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, collect.size());
}
@Data
@AllArgsConstructor
static class Result<T> {
private T data;
private int count; // 추가필드
}
@Data
@AllArgsConstructor
static class MemberDTO {
private String name;
}
회원조회 API 동작 결과
728x90
반응형
'Dev > JPA' 카테고리의 다른 글
[JPA] JPA 활용 II - API 개발 고급 (지연로딩과 조회성능 최적화) (0) | 2021.10.04 |
---|---|
[JPA] JPA 활용 II - API 개발 고급 (준비) (0) | 2021.09.29 |
[JPA] JPA 활용 II - 강좌 소개 (0) | 2021.09.29 |
[JPA] JPA 활용 I - 웹 계층 개발 (2) (0) | 2021.09.28 |
[JPA] JPA 활용 I - 웹 계층 개발 (1) (0) | 2021.09.24 |
댓글