.http 파일 사용하기
리뷰어님의 추천으로 인텔리제이에서 Postman과 비슷한 기능을 수행 할 수 있다는 것을 알게 되었습니다. 바로 .http 파일을 사용하는 것인데요, 인텔리제이에서 디렉토리에 .http 파일을 만들면 다음과 같은 ui가 자동으로 구성됩니다.
그리고 application을 구동한 다음 위 초록색 실행버튼을 누르면 Postman처럼 요청이 갑니다. Response에 대해서도 아래 화면에서 확인이 가능합니다.
헤더나 바디까지도 지정해줄 수 있다는 점에서 좋은 것 같습니다. 또한 이 자체로 API 문서가 되는 셈이고, 버전 관리도 가능해서 유용하게 사용할 수 있을 것 같다는 생각이 드네요. 그리고 요청을 아래처럼 여러개 정의하는 것도 가능합니다.
그렇다면.. E2E 테스트를 굳이 코드레벨에서 하지 않고 .http 파일로 대체해도 나쁘지 않을 것 같다는 생각이 듭니다. 테스트 코드를 통해 E2E 테스트를 하는 경우 코드나 너무 길어지고 복잡하다는 단점이 있었는데 .http 파일을 사용하는 경우 보다 클라이언트 관점에서 테스트가 가능하니까요. 하지만 .http 파일은 인텔리제이 JetBrains IDE의 고유 기능이므로 다른 IDE에서는 사용이 불가능해 살짝 애매하다는 생각도 드는 것 같습니다 😅
관련 아티클: https://jojoldu.tistory.com/266
계층형 패키지 구조
이번 미션에서도 계층형 패키지 구조를 적용했습니다.
주변 크루들을 보니 보통 계층형 패키지 구조 vs. 도메인형 패키지 구조로 의견이 많이 갈리는 것 같았습니다. 저는 개인적으로 계층형 패키지 구조가 직관적이라고 생각하고 계층 간 의존성을 잘 나타낸다고 생각하기 때문에 해당 구조를 선택했는데요. 사실 도메인형 패키지 구조와 계층형 패키지 구조가 완전히 독립적인 개념이라고는 생각이 들지는 않는 것 같습니다. 계층형 패키지 구조에서 도메인이 방대해져 분리하게 되면 그것이 도메인형 패키지 구조라고 생각하기 때문입니다. 패키지 구조에는 여러 접근방법이 있을 것 같은데, 각각의 장단점이 있고 상황에 맞춰서 판단해야 한다고 생각합니다.
Optional 잘 사용하기
이전까지 직접적인 null 참조를 막기 위해 Optional을 다음과 같이 사용했습니다.
Optional<Test> test = ...;
if (test.isPresent()) {
doSomething();
}
하지만 이렇게 사용했을 경우 아래와 코드 구조상으로 다른 부분이 없습니다. 물론 null을 직접적으로 다루지 않는다는 차이점이 있지만요.
Test test = ...;
if (test != null) {
doSomething();
}
그러다가 Optional에 대한 한 아티클을 보게 되었고, Optional 사용 방법을 함수형으로 바꿨습니다. 즉, Optional을 현재는 Stream처럼 다루고 있습니다. 따라서 위와 같은 조건 분기가 사라지게 되었습니다.
@Transactional
public void create(Product product) {
productRepository.findByName(product.getName())
.ifPresent(ignored -> {
throw new IllegalArgumentException("이미 동일한 이름을 가진 상품이 존재합니다.");
});
productRepository.insert(product);
}
관련해서 아티클 첨부합니다.
https://www.daleseo.com/java8-optional-effective/
Interceptor, ArgumentResolver
이번 미션을 통해 Inteceptor, ArgumentResolver를 처음 사용해보았습니다. 둘 다 스프링 컨텍스트 내에서 실행되는 친구들인데(Filter는 서블릿 컨텍스트 내에서 실행되므로 차이가 존재합니다) 적절히 사용한다면 많은 이점이 있을 것 같았습니다.
우선 Interceptor의 경우 인증이나 로깅 등에 사용할 수 있었습니다. ArgumentResolver의 경우 네이밍에서 유추할 수 있듯이(인자 변압기) 컨트롤러 핸들러 메소드에서 인자가 많고 이들을 하나로 합치고 싶은 경우 사용할 수 있을 것 같았습니다. 사실 하나로 합치는 작업은 RequestBody나 ModelAttribute를 사용하면 되는 일이긴 한데, ArgumentResolver의 경우 논리적으로 합치는 작업에도 사용이 가능하므로 유용해보입니다.
크루들 사이에서 Interceptor나 ArgumentResolver가 service 계층에 의존해도 되느냐? 에 대한 이야기가 많이 돌았는데, 제 개인적인 생각은 의존해도 된다입니다. DispatcherSevlet이 동작하는 시점부터는 모두 표현계층이라고 생각하기 때문입니다.
도메인 엔티티를 표현 계층으로 반환했을 때의 문제점
이번 미션은 서비스가 도메인 엔티티를 반환하도록 설계했습니다. 서비스가 표현 계층의 컨텍스트에 묶이지 않게 하기 위함이었는데요, 도메인 엔티티를 반환하지 말아야 한다는 주장도 팽배해서 무슨 문제가 생길 수 있는지 고민해보았습니다.
- 핸들러 메소드가 여러 서비스를 호출해 사용하는 경우, 도메인 객체를 직접 다뤄야 할 가능성이 생긴다. (도메인 엔티티 매핑 작업 등)
- 도메인 엔티티의 로직을 표현 계층에서 실행시킬 가능성이 열려있다. 즉, 도메인 엔티티 상태 변환의 책임이 비즈니스 레이어에게만 국한되지 않는다.
- 도메인 엔티티의 상태 역시 노출된다. 불필요한 데이터, 혹은 보안에 민감한 데이터가 표현 계층으로 전달될 수 있다.
- 표현 계층과 비즈니스 계층의 결합도가 높아진다. 도메인 엔티티의 변경 여파가 표현 계층에 직접적으로 전파된다.
사실 2번, 3번은 반문의 여지가 존재하기는 합니다. '도메인 엔티티를 표현 계층에서 다루더라도, 로직을 수행하지만 않으면 되는 거 아니야? 상태도 마찬가지! 클라이언트에게 전달만 안하면 되는 거 아니야?' 라고 말이죠.
이에 대한 제 생각은 이렇습니다. 우선 저는 개발 과정에서 언제나 실수는 발생할 수 있다고 생각합니다. 다른 개발자가 오용할 여지도 충분하고, 서비스가 장기적으로 성장하고 있다고 한다면 코드를 작성한 개발자마저 본래 의도를 잊게 될 수도 있습니다. 불가능하냐, 가능한데 하지 않느냐는 큰 차이가 있다고 생각합니다.
따라서 앞으로는 도메인 엔티티를 반환하지 않도록 설계할 것 같은데.. 이는 조금 더 고민해봐야겠습니다. 이는 이상적인 에피소드이고 현실을 고려했을 때는 또 바뀔 수 있으니까요.
'우테코 5기' 카테고리의 다른 글
도메인 엔티티 ID 부여에 대한 주관적인 생각 (0) | 2023.05.27 |
---|---|
HTTPS 개념 및 EC2로 배포한 서버에 적용하기(nginx + cerbot) (3) | 2023.05.25 |
[레벨 2 미션] 웹 장바구니 미션 학습 기록 (1) (1) | 2023.05.14 |
[레벨 2 미션] 웹 자동차 경주 미션 학습 기록 (0) | 2023.05.07 |
[트러블슈팅] Interceptor 생성으로 인해 컨트롤러 테스트가 깨지는 경우 (8) | 2023.05.05 |