2024. 3. 31. 17:04ㆍSpring/Spring 웹 MVC
[ 컨트롤러의 역할 분리에 관해 알아야 하는 이유 ]
스프링 MVC를 가장 먼저 배웠을 땐 컨트롤러 계층에서의 스프링 MVC 중요성을 잘 이해하지 못했다. 왜냐하면 단순히 요청만 받고 응답만 내보내면 된다고 생각했기 때문이다. 특히나 처음 프로젝트를 진행했을 때에는 프로젝트에서 구조가 컨트롤러는 서비스를 위임받고 서비스는 리포지토리를 위임받은 구조에 가까웠다. 그러다보니 중요성을 잘못 느꼈다. 그런데 이번에 우빈님의 테스트 코드 강의를 듣고 내 개인 프로젝트에 그 내용을 적용시키는 과정에서 한계를 부딪혔다. 코드를 작성하면서 나는 과연 각각의 역할을 적절하게 잘 분리하고 있나라는 의문이 들었다. 테스트 코드를 작성하고 생각을 해보며 스프링 필터랑 인터셉터가 언제 시작되고 검증 부분은 누가 어떻게 실행하고 결과는 어떻게 나오는지를 잘 모르고 있다는 느낌이 들었다. 그러니 컨트롤러 하나의 API 메서드가 책임이 과도해지는 느낌을 받았다. 하지만, 그걸 분리해 관리할 방법을 잘 몰라서 구현은 했지만 이상한 걸 느끼면서도 수정할 방법을 생각하지 못하고 있다고 생각했다. 그래서 개인적으로 하던 걸 잠시 놓고 잠깐 과거 전공 수업 때 들었던 운영체제 강의를 정리하고 있었다. 이제는 뭐가 잘못됐는지 알았으니 글들을 보고 문서도 읽어보면서 몰랐던 내용들에 관해서 한 번 정리를 해보고자 한다.
[ WAS가 받은 요청이 컨트롤러로 오기까지 ]
이전에 Layered Architecture에 따른 폴더 구조에 관해서 이야기를 한 적이 있었다. 다시 이야기를 하면 스프링 자체는 그림과 같이 WAS에서 HTTP 요청을 받는다. 그러면 요청에 따라 매핑된 컨트롤러를 찾아서 처리를 하고 컨트롤러 내부에서는 안에 주입 받은 내용을 통해 처리하고 응답 결과에 따라 처리를 해준다.
프로젝트에서 스프링 시큐리티를 통한 사용자 인증/인가 기능, 검증 기능 등을 추가하다보니 평소에는 아래처럼 서비스나 리포지토리 로직을 위임하는 코드 작성을 경험했겠지만 점점 뭔가 이상해짐을 느낄 것이다.
@PostMapping("/register")
public ResponseEntity<Post> registerPost(@RequestBody BookRegisterDto registerDto) {
Member findMember = memberRepository.findByUserEmail(dto.getUserEmail);
Book book = registerDto.makeBookWithMember(findMember);
bookRepository.save(post);
return ResponseEntity.ok(book);
}
그렇게 아래와 같은 코드를 작성하고 이런 생각이 들 것이다.
"컨트롤러 코드가 비대해지고 반복되는 코드가 너무 많아지는데,
이걸 어떻게 처리해야 할 지 모르겠다."
@PostMapping("/requestChat")
public ResponseEntity<?> requestChat(@RequestHeader("AccessToken") String accessToken
, @RequestHeader(value = "RefreshToken", required = false) String refreshToken
, @RequestBody ChatMessageDto chatMessageDto) {
if (refreshToken == null) {
if (!jwtTokenProvider.validateToken(accessToken))
return tokenService.requestRefreshToken();
else {
return chatService.requestChat(chatMessageDto); // 여기만 바꿔주세요
}
}
else
return tokenService.reissueAccessToken(accessToken, refreshToken);
}
위의 코드는 내가 과거에 토큰 검증 로직을 처리하기 위해서 컨트롤러 단에서 모든 요청을 처리하려고 해서 일어난 일이다. 그러다보니 모든 인증이 필요한 로직마다 컨트롤러에서 헤더값을 꺼내고 검증하는 로직이 추가되었었다. 이 문제를 스프링 AOP를 적용해서 해결을 할 수도 있을 것이다. 하지만, 웹과 관련된 공통관심사를 처리할 수 있게 해주는 필터를 사용하면 의도에 관한 명확성이 AOP보다 더 높다 또한 모든 인증이 필요한 로직을 한 곳에 모아 관리할 수 있게 해준다. 아무튼, 하고 싶은 말은 이러한 현상을 방지하고 문제를 해결하기 위해서는 컨트롤러와 WAS 사이에 웹 공통 관심사를 처리해주는 것에는 무엇이 있는지를 알아야 한다는 것이다.
앞에서 단순하게 HTTP 요청 -> 컨트롤러라고 된 그림에는 사실 더 많은 과정이 숨겨져 있다. 필터, 디스패처 서블릿, 인터셉터들은 컨트롤러로 들어오기 전 요청에 따라 공통적인 추가 로직을 실행해주는 곳들이라고 생각하면 된다. 그림으로 나타내면 아래와 같다. 앞으로의 글에서는 HTTP요청과 컨트롤러 사이 중간과정에서 무슨 일들이 일어나고 어떤 순서로 일어나며 정상 흐름과 예외의 순간 어떻게 처리가 일어나는 지에 관해 천천히 알아보는 시간을 가져보려고 한다.
[ 참고 자료 ]
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 by 김영한
'Spring > Spring 웹 MVC' 카테고리의 다른 글
스프링 웹 MVC 3. 검증 (0) | 2024.04.01 |
---|---|
스프링 웹 MVC 2. 필터 (0) | 2024.03.31 |
스프링 웹 MVC 2 편 - 스프링 메시지 (0) | 2024.02.08 |
스프링 웹 MVC 1편 - HTTP 메시지 컨버터, 요청 매핑 핸들러 어댑터 (0) | 2024.02.06 |
스프링 웹 MVC 1편 - 컨트롤러 작성(메시지 바디, HTTP 응답, 템플릿 응답) (0) | 2024.02.05 |