[김영한 - JPA 실전 활용 2편] 웹 애플리케이션 개발
영한님은 API
를 사용하는 컨트롤러랑 (@RestController
)
화면
을 위해 존재하는 컨트롤러 (@Controller
) 가 있는 패키지를 아예 분리하신다고 한다
왜냐하면 각각의 컨트롤러에서 공통처리 자체를 다르게 해주기 때문에!
예를들어 API
컨트롤러는 예외가 나왔을때 Json으로 예외 메세지 처리를 하는데
화면
컨트롤러는 오류 페이지를 보여준다
- 화면 벨리데이션이 모델에 있다
- 프레젠테이션 계층과 핵심 도에민이 분리가 안 돼는 것이나 마찬가지 !
- 화면마다 API 호출방식 다를수잇다
- 화면이 스펙이 깨진다 !
API 요청 스펙의 DTO가 별도로 필요하다
그냥.. 내부로 받든, 외부로 보내든 엔티티를 내세우면 안돼!
API 스펙에 맞는 전용 DTO를 통해서
어떤 API가 어떤 필드를 필요로 하고 어떤 제약사항이 있는지를 한 번에 알 수 있다
멱등성
같은걸 여러번 호출해도 결과는 같다.
커맨드와 쿼리를 철저히 분리
수정은 커맨드이며 회원객체를 조회해 오는 것은 쿼리이다
이 둘의 책임을 확실히 분리하는 것이 좋다 !
id 정도는 반환해도 괜찮다
Json 반환을 무시하고 싶으면 @JsonIgnore
위 어노테이션을 넣으면 API로 노출될 떄 스프링은 내부적으로 잭슨을 쓰기 떄문에 무시할 수 있다
물론 위처럼 사용하면 안됀다
굉장히 다양한 클라이언트들이 많은 스타일의 API를 요구하는데 엔티티 하나를 막아 놓음으로써 다른 API에서는 활용할 수 있는 여지마저 막아 놓는 것이다
반환 스타일, [] 냐 {} 냐
[
userA : {},
userB : {}
...
]
보다
{
count : 4,
data : [
userA : {},
userB : {}
]
}
가 더 낫다 (확장이 가능하다)
항상 우리는 EAGER
로 처리할 지 심각한 고민에 빠지게 됨
계속해서 발생하는 1 + N
문제 … ! 그냥 EAGER
로 해버리면 그만일텐데 !! ㅠㅠㅠ
만일 JPQL에서 EAGER
로 설정하게 될 경우에 1+N
이 발생!
이유는 JPQL은 em.find()
와 달리 join
문장으로 최적화를 해주지 않고 단순히 SQL
로 번역해서 질의하기 때문이다.
그리고 order를 가져왔는데 막상 member와 delivery의 페치 타입이 EAGER
인데
해당 엔티티에 대한 정보가 없으므로 정보를 가져오기 위하여 추가적인 쿼리가 발생하게 된다 ㅠ
++ 또한 다른 곳(API)에서 굳이 끌고올 정보가 필요없는 곳에서도 끌고 오므로 성능 최적화의 여지가 없어져 버린다
내부적으로 프록시로 감쌀 때
ByteBuddyInterceptor
를 이용한다 !
강의 중간에 해결하기 위한 하나의 방책으로
Hibernate5Module
을 사용하기도 했지만 이거 역시 좋은 방법은 아니야!
DTO
에서 Entity
를 의존하는 것은 문제가 되지 않는다
왜냐하면 중요한 곳에서 별로 안 중요한 곳에 의존하지 않는 것이기 때문에 !!
Entity ← DTO (O)
Entity → DTO (X)
결론은 fetch join을 사용하면 된다 !
select o from Order o
join fetch o.member m
join fetch o.delivbery d
헥사고날 아키텍쳐
인터페이스로 모두 발라낸다
의존성이 외부에서 내부로만 존재
인프라와 도메인 / 비즈니스를 최대한 분리하기 위함 (명확한 관심사의 분리)
JPQL내 new
를 사용한 TO로 바로 조회
select new jpabook.jpashop.repository.order.simplequery.OrderSimpleQueryDto(o.id, m.name, o.orderDate, o.status, d.address)
from Order o
join o.member m
join o.delivery d
미비하지만 데이터를 직접 선택하기에 네트워크 용량이 좀더 적게 든다
- 컬럼 숫자를 최적화할 수 있다
이걸 위한 하나의 클래스를 만들어야 한다
- 재사용성도 떨어진다
코드가 지저분 하다..
물리적으로는 계층이 깨지지 않았지만 논리적으로는 의존관계가 깨졌다
- 화면 API 스펙 요청이 바뀌면 repository 계층도 바뀌어야만 한다
별도로 Repository를 둔다
화면에 의존적인 복잡한 쿼리는 별도의 하위 패키지를 두어 이곳에 몰아 둔다
예) repository.order.simplequery
vs fetch join 으로 모두 가져오기
컬럼 숫자가 많아지면 생각을 해보아야 한다 (10 ~ 30개)
코드가 깔끔하다
구조가 견고하다
쿼리 방식 선택 권장 순서
우선 엔티티를 DTO로 변환하는 방법을 선택한다. (필수)
필요하면 페치 조인으로 성능을 최적화 한다.
- 대부분의 성능 이슈가 해결된다.
- 그래도 안되면 DTO로 직접 조회하는 방법을 사용한다.
- 2번과 3번은
트레이드 오프
영역이다
- 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링
JDBC Template
을 사용해서 SQL을 직접 사용한다.
암달의 법칙
일부의 성능을 극한으로 끌여 올렸어도 그 일부가 전체에서 차지하는 비중이 작다면, 미비하다
반대로 그 일부의 성능을 다소 증가시켰어도 전체에서 차지 하는 비중이 크면 체감이 될 정도로 크다
예를 들어 보조 기억장치 (컴퓨터 용량 : C뜨라이브 등)
보조 기억장치는 일반적으로 주기억장치나 CPU의 레지스터에 비해 느림
때문에 무언가를 작업할 때 가장 느린 이 보조 기억장치에 의존하는 경향이 있음.
즉 전체에서 차지하는 비중이 크다.
때문에 HDD에서 SDD로 바꾸었을 떄 극한의 성능 체감을 느낄 수 있으며,
흔히들 말하는 업그레이드의 가성비 끝판왕은
HDD에서 SSD로 교체하는 것의 재해석이 가능 … ㅋ