현재 프로젝트 환경은 스프링 프레임워크에서 제공하는 이벤트 기반 프로그래밍(Event-driven Programming)을 사용하여 모듈 간의 느슨한 결합을 유지하고 있다.
이벤트 기반 프로그래밍
(+) 느슨한 결합(Loose Coupling)
여러 컴포넌트 간에 직접적인 의존성을 줄일 수 있다.
예를 들어, 이벤트를 발생시키는 객체는 이벤트 리스너를 알 필요가 없으며, 리스너는 이벤트가 발생했을 때만 반응하면 된다. -> 이로 인해 시스템 간의 결합도를 낮출 수 있다.
(+) 비동기 처리
이벤트 리스너는 비동기적으로 동작하여 이벤트 발생 후 별도의 작업을 백그라운드에서 처리하거나, 애플리케이션의 응답성을 높일 수 있다.
(+) 확장성
새로운 기능을 추가할 때 기존 코드의 변경 없이 새로운 이벤트와 리스너를 정의하여 시스템을 확장할 수 있다.
(+) 비즈니스 로직의 분리
이벤트를 사용하여 특정 작업을 처리하는 로직을 다른 컴포넌트로 분리함으로써, 애플리케이션의 유지 보수성을 높일 수 있다.
(+) 재사용 가능성
이벤트와 리스너는 재사용 가능하며, 서로 다른 상황에서 동일한 이벤트를 발생시키고 이를 처리할 수 있는 다양한 리스너를 구현할 수 있다.
모듈 A와 모듈 B가 존재한다고 가정하자.
모듈 A의 클라이언트에서 모듈 B의 서버로의 요청이 필요한 상황일 경우, 모듈 A의 CustomSpringEvent에서 이벤트 발행 후 모듈 B의 @EventListener에서 처리를 진행하고 있다.
하지만 만약 모듈 B의 서버단에서 mv를 반환해야 하는 상황이 온다면?
위의 경우 PRG(POST-Redirect-GET) 패턴을 활용하여 모듈 A와 모듈 B 간의 이벤트 기반 처리를 구현할 수 있다.
PRG 패턴은 일반적으로 폼 제출 후 리다이렉션을 통해 브라우저의 중복 요청을 방지하는 방식으로 사용되지만, 이를 모듈 간의 비동기적인 이벤트 처리와 함께 활용하여 결과를 안전하게 반환하는 방식으로 구현할 수 있다.
모듈 B : 이벤트리스너에서 바로 리다이렉트하는 코드
@Inject
private HttpSession httpSession;
@Autowired
private HttpServletResponse response;
@EventListener(condition = "#event.eventId == 'openXXXPopup'")
public void openXXXPopup(CustomSpringEvent event) {
Map param = (Map) event.getData();
httpSession.setAttribute("param", param);
try {
response.sendRedirect("/redirectURL.do");
} catch (Exception e) {
LOG.error("Exception msg : {}", e.getMessage());
LOG.error("Exception cause : {}", e.getCause());
LOG.error("Exception trace : {}", e.toString());
LOG.error("error stacktrace : {}", e.getStackTrace());
}
event.setResult(param);
}
모듈 B : 리다이렉트 요청을 받은 controller
@RequestMapping(value = "redirectURL.do", method = RequestMethod.GET)
public ModelAndView openXXX(HttpSession session) {
Map param = (Map) session.getAttribute("param");
if(param == null) {
throw new CommonException(this.getClass().getName() + ".openXXX() : check the parameters");
}
ModelAndView mv = new ModelAndView();
Map paramMap = xxxService.openXXX(param);
if(resultMap.isSuccess()) {
mv = new ModelAndView("jsp파일");
mv.addObject("param", paramMap);
} else {
}
return mv;
}
PRG 패턴
1. POST : 사용자가 데이터를 서버에 제출하거나 요청을 발생시킴
2. Redirect : 서버는 데이터 처리를 완료한 후, 클라이언트를 다른 URL로 리다이렉트
3. GET : 클라이언트는 새로운 URL로 GET 요청을 보내고, 이때 최종적으로 처리된 결과를 응답받음
처음에는 위의 PRG 패턴을 사용하여 모듈 B의 이벤트 리스너에서 모듈 B의 컨트롤러로 리다이렉트를 시도하는 코드를 작성하였다. 하지만 이벤트 리스너에서 리다이렉트를 사용하는 방법은 옳지 않다!
(-) 역할 분리 원칙 위반
이벤트 리스너는 비즈니스 로직을 처리하는 곳이어야 하며, HTTP 응답 처리(리다이렉트, 뷰 반환 등)는 컨트롤러의 역할이다. 이벤트 리스너에서 직접적으로 sendRedirect()를 호출하는 것은 역할 분리의 원칙을 위반하게 된다.
이로 인해 응답 처리 로직과 비즈니스 로직이 섞이게 되며, 이는 유지보수성 및 테스트 용이성에 좋지 않은 영향을 미친다.
(-) 리다이렉트가 이벤트 흐름을 방해
이벤트 리스너에서 리다이렉트를 호출하면, 이벤트 처리 흐름이 HTTP 응답을 보내는 시점에 종료된다.
이는 이후의 다른 로직(예: 비즈니스 로직 후 결과 처리)을 실행할 수 없게 만들거나, 원치 않는 사이드 이펙트를 발생시킬 수 있다.
(-) Spring의 MVC 설계 패턴과의 불일치
Spring MVC의 설계 패턴에서는 컨트롤러가 HTTP 요청 및 응답을 처리하는 핵심적인 책임을 가지고 있다. 이벤트 리스너에서 리다이렉트를 처리하는 것은 Spring MVC의 설계 원칙에 맞지 않으며, 이런 방식은 다른 개발자나 유지보수자가 코드를 이해하고 관리하는 데 어려움을 준다.
결론
모듈 A에서 모듈 B의 컨트롤러를 직접 호출하는 것이 더 나은 접근법이다.
-> 이벤트 기반 처리와 리다이렉트를 복잡하게 엮지 않고, 모듈 간의 책임 분리를 명확히 할 수 있음
처음에는 모듈 A에서 모듈 B를 바로 호출하는 것이 모듈간 의존성을 높인다고 생각하여 여러 방법을 찾아보았으나
결국 이 방법이 차선책이라는 것 같다..
'개발' 카테고리의 다른 글
| XMLHttpRequest 객체에 대해 : 서버에 실제 파일이 존재하는지 js로 확인하기 (0) | 2025.01.21 |
|---|---|
| 인텔리제이로 jar 생성하기 (0) | 2025.01.09 |
| 로컬호스트에 https 적용하기 : 톰캣에 ssl 인증서 적용 방법 (0) | 2024.11.05 |
| Sticky Session & Session Clustering (3) | 2024.11.04 |
| json 데이터 암호화 문제 해결, @RequestParam과 @RequestBody (1) | 2024.10.09 |