자바 ORM 표준 JPA 프로그래밍 교재를 통해 공부하며 고민했던 내용입니다.
JPA를 공부하며 의문이 생겼다.
JPA를 사용하면 Entity와 DTO를 분리해서 사용해야 된다고 한다.
이전에 Mybatis를 사용할 때는 mapper가 DAO의 역할을 하기 때문에 별도로 만들지 않고 DTO만으로 처리를 했었다.
이렇다고 배웠다..
스프링 공부를 시작한 지 얼마 안 되었을 때라서 Member에 대한 API는 모두 MemberDTO로 처리하였고, API를 만들다 보니 필요 스펙에 따라 온갖 어노테이션을 갖다 붙였다.
지금 다시 열어보면 정말 최악의 코드인 것 같은데 그때 당시에는 잘 짰다고 생각했던..
어쩌면 이러한 실수? 무지? 때문인지 JPA를 공부하면서 크게 깨달음을 얻게 되었다.
MyBatis를 사용했을 당시의 코드를 보고 간단하게 문제점을 생각해보면
- 하나의 Dto로 모든 API를 해결하려고 했음
- Member Dto에 죄다 @NotNull이 붙어있는데 API 스펙에 따라 필요 없는 정보가 있을 수 있고 @Valid를 통해 검증을 하는 API가 있다면??..(없을 수가 없음) API가 제대로 작동하지 않는다.
- API는 요구사항에 따라 계속 변화가 생겨날 테고 모두가 공통적인 스펙을 사용한다는 것은 말이 안 됨.
통합적인 DTO를 설계해서 사용하는 것은 옳다고 생각하나, 이 당시에는 별다른 생각 없이 API를 추가할 때마다 어노테이션을 마구 추가하였음.
다시 설계해서 분리를 하라고 하면 화가 날 것 같고 귀찮을 것 같다. -> 유지보수 실패
간단하게만 살펴보았고, 그 외에도 쿼리문에도 문제가 많았다.. Dto를 제대로 활용하지 못하고 기계적으로 만든 게 눈에 보임.. 다시 본론으로 들어가기에 앞서
엔티티와 DTO를 분리해야 하는 이유에 대해서 설명이 잘 된 기술 블로그를 참고하시면 좋을 것 같습니다.
https://mangkyu.tistory.com/192
[Spring] 엔티티(Entity) 또는 도메인 객체(Domain Object)와 DTO를 분리해야 하는 이유
개발을 하다 보면 API의 요청이나 응답을 처리할 때 또는 다른 계청으로 넘기는 파라미터가 너무 많은 시점에 별도의 DTO를 생성해야 하나 고민을 하는 시점이 생깁니다. 개인적으로는 간단한 애
mangkyu.tistory.com
저는 설계를 하다가 발생했던 문제점과 어떻게 변경을 하였는지에 대해 설명드리도록 하겠습니다.
1. API 스펙에 의한 문제점
설계 초기에는 책에 나와있는 DB설계 다이어그램을 보고 Member Entity를 구성하였습니다.
초기 단계에서는 회원가입의 API만을 생각하여 회원의 정보에 NON_NULL이라는 설정을 하였습니다.
그럼 간단하게 API를 만들 때 @Valid만 사용하면 알아서 검증을 해줄 것입니다.
그런데, 여기서 사용자가 주문한 상품을 관리하는 OrderEntity와 Member가 매핑 관계가 되면 MemberEntity는 다음과 같이 변경될 것입니다.
그렇게 되면 컴파일 과정에서 오류가 생길까요? 정답은 아니다입니다.
그렇다면 회원가입 API가 정상 작동을 할까요? 이것 또한 아니다입니다.
orders라는 List 필드가 생겼는데 @JsonInclude(JsonInclude.Include.NON_NULL)과 @Valid 때문에 api호출 시 정상작동을 하지 않게 되는 것이죠. 우리는 이전에 있던 name, city, street, zipcode 필드의 값만 입력받도록 api스펙을 설정했기 때문입니다. 즉, Entity에 직접 접근하다 보니 요구사항이 변경됨에 따라 Entity가 변경되었고, API 스펙이 같이 변경돼버린 것입니다.
@JsonInclude(JsonInclude.Include.NON_NULL)을 애초에 사용하지 않고 다른 방식으로 Validation을 진행하면 문제없는 게 아니냐라고 하실 수 있지만, Entity는 어떤 요구사항에 의해 어떤 방향으로든 계속 변경될 수 있기에 모든 상황을 예측하여 설계하는 것은 말이 안 됩니다.
그리고 개발자에 따라 나는 하드코딩 안 하고 Lombok 같은 라이브러리를 잘 활용해서 어노테이션들로만 잘 처리할 거야. 하게 되면 하나의 엔티티에 엄청난 어노테이션들이 덕지덕지 붙어 다른 사람은 이해하기 힘든 어쩌면 본인도 이해하기 어려운 클래스가 탄생할 것입니다.
그렇기 때문에 우리 모두가 편하기 위해서는 Entity와 DTO를 분리하여 해당 API에게만 필요한 스펙을 제공하는 것이 유지보수나 코드 이해 관점에서 유리할 것입니다.
Entity와 Dto를 분리하여 API를 설계해 보겠습니다.
JoinMemberRequest를 Member Entity 대신에 RequestBody와 매핑
- 엔티티와 프레젠테이션 계층을 위한 로직을 분리할 수 있다.
- 엔티티와 API 스펙을 명확하게 분리할 수 있다.
- 엔티티가 변해도 API 스펙이 변하지 않는다.
하지만 여기서 끝이 아니다. 처음에 이렇게 변경을 하였을 땐 엔티티에 대해 set메서드를 마구 사용해서 코드가 지저분해 보였다. 역시나 알아보니 JPA를 사용할 땐 Entity의 Setter를 사용하는 게 문제라고 한다.
Entity의 Setter 사용을 지양하는 이유
- Setter의 사용은 의도가 분명하지 않고 객체를 언제든지 변경할 수 있는 상태가 되어서 객체의 안전성 보장 X
- Setter를 사용해 엔티티를 업데이트하게 되면 어디서 누구에 의해 발생했는지 추적하기가 힘들어짐.
->값 변경이 필요한 경우 의미 있는 메서드를 생성하여 사용!
이에 대해 참고한 글입니다.
JPA 엔티티 작성 - Setter 금지
엔티티를 작성함에 제가 생각하는 몇가지 원칙(?)이 있습니다.그중 엔티티(객체)의 Setter 사용 금지 원칙(?) 에 대해 알아보겠습니다.엔티티를 작성할 때 습관적으로 모든 필드에 Setter를 생성하는
velog.io
https://developer-ping9.tistory.com/225
[Spring JPA] Setter를 사용하지 않는다.
# Setter 사용을 지양하는 이유 의도가 무엇인지 파악이 어렵다. (생성인지, 업데이트인지) 객체의 일관성을 유지하기 어렵다. (특정 메서드가 아닌 모든영역에서 접근이 가능하다) # Lombok.config 에
developer-ping9.tistory.com
@Builder를 사용하여 리팩터링 한 결과
Builder 사용 시 참고
[Lombok] 올바른 Lombok 사용법 - @Builder
우리가 Spring에서 자주 볼 수 있는 Lombok들에 대해 알아보겠습니다 아래와 같은 코드가 있다고 할 때 이를 어떻게 리펙토링 할 수 있을까요? @Getter @Setter // 문제 1. 객체가 무분별하게 변경될 가능
dev-jhl.tistory.com
틀린 부분 혹은 지적 댓글 감사합니다!
'JPA' 카테고리의 다른 글
Querydsl을 이용한 Cursor기반 페이징 API 구현 과정 (2) | 2022.12.11 |
---|---|
지연 로딩과 조회 성능 최적화 (0) | 2022.07.22 |