반응형
API 개발 기본
포스트맨을 설치해서 API 개발 예제를 실습해보자.
포스트맨 설치 : https://www.getpostman.com
회원 등록 API
package jpabook.jpashop.domain;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String name;
@Embedded
private Address address;
// mappedBy : 연관관계 주인이 아님을 지정
// (여기서 수정해도 order의 값이 변경되지 않는다.)
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
- 회원 엔티티는 다음과 같다.
회원 등록 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;
@RestController
@RequiredArgsConstructor
public class MemberApiController {
private final MemberService memberService;
@PostMapping("/api/v1/members")
public CreateMemberResponse savedMemberV1(@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;
}
}
}
- v1 상황 : 요청 값으로
Member
엔티티를 직접 받고 있다.name
검증을 위해선 엔티티에validation
을 적용해야하는 문제점 발생- 여러 API 요청을 처리하기 위해 엔티티가 변경되면 API 스펙이 변하는 문제 발생
API 요청 스펙에 맞추어 별도의 DTO를 생성하여 파라미터로 받는다.
회원 등록 API (V2)
@PostMapping("/api/v2/members")
public CreateMemberResponse savedMemberV2(@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 {
@NotEmpty
private String name;
}
- V2 상황 : 등록DTO인
CreateMemberRequest
를 생성하여 해당 부분만 검증하는 방식- 엔티티와 프레젠테이션 계층을 위한 로직을 분리
- 엔티티와 API 스펙을 명확하게 분리
회원 수정 API
@PatchMapping("/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;
}
- V2 방식으로 수정을 적용시켰다. 수정할 땐
PUT
,PATCH
,POST
방식이 있다. 전체를 업데이트 하는 경우PUT
을 부분 업데이트 하는 경우PATCH
,POST
방식을 사용하는 것이 REST 스타일에 올바르다.
@Transactional
public void update(Long id, String name) {
Member member = memberRepository.findOne(id);
member.setName(name);
}
update
는 다음과 같이 서비스 단에서 변경감지를 통해 변경하도록 구현되어 있다.
회원 조회 API
data
와 같이 별도의 배열로 감싸면 확장성에 용이해진다.
회원 조회 API (V1)
@GetMapping("/api/v1/members")
public List<Member> membersV1() {
return memberService.findMembers();
}
- V1 상황 : 회원 정보만 필요한 상황인데
orders
등의 불필요한 정보들도 노출되고 있다.- 엔티티의 모든 값이 노출되는 것은 올바르지 않고, 별도의 DTO를 통해 응답 스펙에 맞추어 반환하자.
회원 조회 API (V2)
@GetMapping("/api/v2/members")
public Result membersV2() {
List<Member> findMembers = memberService.findMembers();
// 엔티티 -> DTO 변환
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;
}
- V2 상황 : 엔티티를 DTO로 변환하였고,
Result
클래스로 컬렉션을 감싸서 확장성을 높혔다.
본 포스팅은 인프런 김영한님 강의(실전! 스프링 부트와 JPA 활용2)를 토대로 정리한 내용입니다.
반응형