Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 그리디
- java
- 알고리즘
- Servlet
- 스프링
- JDBC
- 김영한
- pointcut
- Thymeleaf
- AOP
- springdatajpa
- Greedy
- spring
- 자바
- http
- Exception
- db
- jpa
- Spring Boot
- 인프런
- QueryDSL
- kotlin
- transaction
- 스프링 핵심 기능
- 백준
- 스프링 핵심 원리
- Proxy
- JPQL
- Android
- SpringBoot
Archives
- Today
- Total
개발자되기 프로젝트
회원 등록, 수정, 조회 API 본문
* @RestController = @Controller + @ReponseBody
* @RequestBody : JSON 데이터를 Member로 바꿔줌.
1.회원 등록 : 엔티티를 @RequestBody에 직접 매핑
- api에서 엔티티를 직접 사용하면 여러 문제점이 있다
- 엔티티에 프레젠테이션 계층을 위한 로직이 추가됨
- 엔티티에 API검증을 위한 로직이 들어감.(@NotEmpty 등)
- 엔티티가 변경되면 API스펙이 변한다.
- ㅜㅜ
@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;
}
}
@Entity
@Getter@Setter
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@NotEmpty
private String name;
}
만약 name을 userName으로 변경한다면 API spec이 변경된다. 클라이언트에서 name으로 사용하고 있었다면 장애가 발생하게 된다. ㅜㅜ
따라서 api spec을 위한 별도의 dto가 필요해!
2. 회원 등록 : DTO 활용
- 기존 Member를 대신에 DTO인 CreateMemberRequest를 활용.
- 엔티티와 프레젠테이션 계층을 위한 로직을 분리함!
- 엔티티와 API 스펙을 명확하게 분리 가능.
- 따라서 엔티티가 변해도 API 스펙이 변하지 않는다.
- api는 항상 요청이 들어오고 나가는것은 절대 엔티티 사용하지 않고 dto사용하자.
- entity를 외부로 노출하지 말자
@PostMapping("/api/v2/members")
public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request){
Member member = new Member();
member.setName(request.name);
Long id = memberService.join(member);
return new CreateMemberResponse(id);
}
@Data
static class CreateMemberRequest{
private String name;
}
@Data
static class CreateMemberResponse{
private Long id;
public CreateMemberResponse(Long id) {
this.id = id;
}
}
3. 회원 수정
- put : 덮어쓰기
- 사실 여기서는 patch나 post를 활용해야 함 ㅜ
@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(id, findMember.getName());
}
@Data
static class UpdateMemberRequest{
private String name;
}
@Data
@AllArgsConstructor
static class UpdateMemberResponse{
private Long id;
private String name;
}
@Transactional
public void update(Long id, String name) {
Member member = memberRepository.findOne(id);
member.setName(name);
}
Hibernate:
update
member
set
city=?,
street=?,
zipcode=?,
name=?
where
member_id=?
4. 회원 조회 : 엔티티 직접 사용
@GetMapping("/api/v1/members")
public List<Member> membersV1(){
return memberService.findMembers();
}
[
{
"id": 1,
"name": "1번",
"address": {
"city": "ㅇㅇ",
"street": "ㅏㅏ",
"zipcode": "55"
},
"orderList": []
},
{
"id": 2,
"name": "2",
"address": {
"city": "서",
"street": "56",
"zipcode": "2321"
},
"orderList": []
}
]
- 엔티티를 직접 노출하면 엔티티의 모든 정보가 노출됨.
- @JsonIgnore을 통해 제한할 수 는 있다.
@JsonIgnore @OneToMany(mappedBy = "member") private List<Order> orderList = new ArrayList<>();
- 하지만 회원과 관련된 조회 API는 하나는 아닐 것이다..
- 따라서 엔티티에 요런게 들어가기 시작하면 답이 없다.
- 또한 엔티티에 화면에 뿌리기 위한 로직이 추가되는 것..(프레젠테이션 계측을 위한 로직 추가됨)
- 엔티티의 순수성이 깨짐.
- 또한 엔티티 변경시 API 스펙이 변경됨 ㅜㅜㅜㅜ 굉장히 심각함
- 기본적으로 엔티티의 모든 값이 노출된다. 응답 스펙을 맞추기 위해 로직이 추가된다.
(@JsonIgnore, 별도의 뷰 로직 등등) - 실무에서는 같은 엔티티에 대해 API가 용도에 따라 다양하게 만들어지는데,
한 엔티티에 각각의 API를 위한 프레젠테이션 응답 로직을 담기는 어렵다. - 엔티티가 변경되면 API 스펙이 변한다.
- 추가로 컬렉션을 직접 반환하면 항후 API 스펙을 변경하기 어렵다.(별도의 Result 클래스 생성으로 해결)
- 유연성이 떨어진다.
- 컬렉션을 직접 반환하는 경우 json 배열로 나가기 때문에 다른 내용을 추가할 수 없음..
5. 회원 조회 : DTO 사용
- 엔티티를 DTO로 변환해서 반환한다.
- 엔티티가 변해도 API 스펙이 변경되지 않는다.
- 추가로 Result 클래스로 컬렉션을 감싸서 향후 필요한 필드 추가 가능.
- generic으로 data를 받았다.
- result를 return하는 경우 json배열로 나가는 것이 아니라
- json의 data안에 조회한 memberList가 json형태로 나가게 된다.
@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);
}
@Data
@AllArgsConstructor
static class Result<T>{
private T data;
}
@Data
@AllArgsConstructor
static class MemberDto{
private String name;
}
필드를 추가해 보자 어떻게 될까?
@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;
}
6. 참고) stream.map
/**
* Returns a stream consisting of the results of applying the given
* function to the elements of this stream.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param <R> The element type of the new stream
* @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function to apply to each element
* @return the new stream
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
7. GitHub : 210820 DTO
8. 요약
API 요청이 들어올때, 응답이 나갈 때 Entity를 외부에 노출하지 말고, DTO를 사용하자.
'인프런 > [인프런] Springboot와 JPA활용 2' 카테고리의 다른 글
지연 로딩과 조회 성능 최적화(JPA에서 DTO 바로 조회) (0) | 2021.08.23 |
---|---|
지연 로딩과 조회 성능 최적화(FETCH JOIN) (0) | 2021.08.22 |
지연로딩과 성능최적화(DTO, N+1) (0) | 2021.08.22 |
지연로딩과 조회성능 최적화(엔티티 노출) (0) | 2021.08.22 |
API 개발 고급 소개 1 (0) | 2021.08.22 |
Comments