JPA : JPA를 활용한 회원 관련 API 개발
- Spring/JPA
- 2022. 1. 15.
이 포스팅은 인프런 김영한님의 JPA 강의를 듣고 복습하며 정리한 내용입니다.
API 개발
어느 순간 앱을 개발해야 할 시점이 왔다고 가정하자. 이 시점에서는 기존에 웹을 개발하던 것처럼 View Template을 반환하는 것이 아닌 API 형식으로 데이터를 주고 받아야한다. 어플리케이션은 API 데이터를 받아, 클라이언트에서 필요한 것들을 구현해주기 때문이다.
API 개발을 할 때 주의해야 할 사항
1. 엔티티를 파라메터로 받지 마라.
엔티티는 해당 API 뿐만 아니라 정말 다양한 곳에서 사용될 수 있다. 예를 들어 엔티티의 name이 userName으로 바뀌었다고 가정해보자. 이럴 경우, 어느 순간 특정 API 컨트롤러가 고장이 나는 경우가 발생할 수 있다. 따라서 DTO(Data Transfer Obejct)를 이용해서 클라이언트로 값을 받는다.
2. 엔티티를 응답으로 보내지마라.
동일한 이유로 엔티티를 응답으로 내보내면 안된다. 클라이언트는 특정 API 스펙에 의존하고 있을텐데, 이 때 엔티티가 바뀌게 되면 클라이언트에서는 먹통이 된다. 따라서 응답으로 보낼 때는 DTO(Data Transfer Object)를 활용해서 응답한다.
public class Member {
@JsonIgnore
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
또한, 엔티티를 돌려주게 될 경우 도메인 계층이 웹 계층에 의존하게 된다는 문제도 있다. 예를 들어 엔티티를 응답할 때, Member 안의 orders라는 값이 API 스펙에서는 필요없다고 해보자. API SPEC을 맞추기 위해 @JsonIgnore를 사용할 수 있다. 이 경우, 엔티티에 응답을 위한 웹계층 로직이 들어가버린다. 이렇게 되며 양방향 의존관계가 생긴다는 문제가 있다.
이렇다고 문제가 해결되지도 않는다. 다른 API에서는 저 orders라는 값이 필요할 수도 있기 때문에 근본적인 문제 해결조차 되지 않는다.
3. 응답은 가급적이면 JSON List 형식으로 보내지 않는다.
JSON List 형식으로 보내게 될 경우, 이 응답에 변수를 추가하기가 쉽지 않다. 왼쪽 같은 경우에 "count"라는 스펙이 추가된다면, 넣을 방법이 매우 복잡해진다. 반면 오른쪽처럼 리스트를 JSON 안쪽에 포함시켜주면, 손쉽게 "count"라는 스펙을 추가할 수 있다.
즉, LIST<DTO>를 한번 더 DTO로 감싸서 응답을 내보내 주게 되면 오른쪽과 같이 편리하게 관리할 수 있다.
코드 실습
엔티티를 파라메터로 받는 회원등록 API
@PostMapping("/api/v1/members")
public CreateMemberResponse saveMemberV1(@RequestBody Member member) {
Long saveId = memberService.join(member);
return new CreateMemberResponse(saveId);
}
- 엔티티를 API에서 직접 받기 때문에 엔티티 수정되면 큰 장애가 발생할 수 있다.
DTO를 파라메터로 받는 회원등록 API
@PostMapping("/api/v2/members")
public CreateMemberResponse saveMemberV2(@RequestBody CreateMemberRequest request) {
Member member = new Member();
member.setName(request.getName());
Long saveId = memberService.join(member);
return new CreateMemberResponse(saveId);
}
@Getter
static class CreateMemberRequest{
private String name;
}
@Data
@NoArgsConstructor
static class CreateMemberResponse {
private Long id;
public CreateMemberResponse(Long id) {
this.id = id;
}
}
- DTO를 통해서 파라메터를 받고, 응답해주기 때문에 엔티티 변경과 좀 더 무관해진다.
엔티티를 받아 수정, 엔티티로 응답하는 수정 API
@PutMapping("/api/v2/members/{memberId}")
public UpdateMemberResponse updateMemberV2(@RequestBody UpdateMemberRequest request,
@PathVariable(name = "memberId") Long id) {
memberService.updateMember(id, request.getName());
Member findMember = memberService.findOne(id);
return new UpdateMemberResponse(findMember.getId(), findMember.getName());
}
@Getter
static class UpdateMemberRequest {
private String name;
public UpdateMemberRequest(String name) {
this.name = name;
}
}
@Getter
@AllArgsConstructor
static class UpdateMemberResponse {
private Long id;
private String name;
}
- DTO를 통해서 파라메터를 받고, 응답해주기 때문에 엔티티 변경과 좀 더 무관해진다.
엔티티를 반환하는 회원조회 API
@GetMapping("/api/v1/members")
public List<Member> membersV1() {
return memberService.findMembers();
}
- 엔티티를 직접 돌려주기 때문에 엔티티 변경에 민감하다.
- API 응답 SPEC에는 필요없는데 엔티티에 있는 값이 있다면 @JsonIgnore 처리를 해야한다. 즉, 도메인이 웹에 의존하게 된다.
엔티티 리스트를 DTO로 감싸서 응답하는 회원조회 API
@GetMapping("/api/v3/members")
public Result membersV3() {
List<Member> members = memberService.findMembers();
List<MemberDto> collect = members.stream()
.map(member -> new MemberDto(member.getName()))
.collect(Collectors.toList());
return new Result(collect);
}
@Data
static class Result {
private List<MemberDto> members;
public Result(List<MemberDto> members) {
this.members = members;
}
}
@Data
static class MemberDto {
private String name;
public MemberDto(String name) {
this.name = name;
}
}
- DTO 리스트를 다시 한번 DTO로 감싸서 반환했다.
- 엔티티 변경에 둔감하고, DTO에 다른 Spec을 추가하는데도 문제가 없다.
'Spring > JPA' 카테고리의 다른 글
JPA : Order와 관련된 API 구현하기 (0) | 2022.01.15 |
---|---|
JPA : JPA를 DTO로 바로 접근하기 (0) | 2022.01.15 |
JPA : @Transacitonal 관련 정리 (0) | 2022.01.10 |
JPA 프로젝트를 다시 하면서 정리 (0) | 2022.01.09 |
JPA : Fetch Join (0) | 2021.12.02 |