항해99 71일차 TIL1 - 비동기 기반의 이벤트 리스너가 필요한 이유 - (2)
이 글을 보는 누군가는 나와 같은 실수를 안하길 DDD START!는 도메인 주도 개발 시작하기의 구판이다. 도메인 주도 개발 혹은 이벤트에 대해 궁금하다면 신판을 구매하는 것이 더 좋을수 있어보입니다. 주요 차이라고 한다면 구판에서는 이벤트 핸들러, 디스패처를 직접 구현하지만 신판에서는 스프링이 제공하는 ApplicationEventPublisher 를 사용하여 예제를 설명하십니다. 우선 70일차에서 정리했던 시스템 간 강결합 문제를 다시 한 번 써보겠습니다.
시스템 강결합 문제의 단점
변화가 일어 날 때 서비스간에 영향을 받는다.
A기능과 B기능이 있다고 했을 때 B의 변경이 A에도 영향을 미친다. 또한 C기능이 A, B와 결합되어야 할 때도 문제이다. 기능들이 강하게 결합되다보면 A의 본래 기능이 무엇인지 파악하기 힘들수도 있습니다.
성능 문제
A기능을 수행하는데 B가 연관이 있기 때문에 B 기능 수행이 느려진다면 A도 느려집니다.
트랜잭션 처리
서비스간에 강하게 결합되어 있다면 설계에 따라 예상치 못한 트랜잭션 처리가 발생할 수 있습니다.
예를 들면 B 기능 실패로 인해 A 기능까지 함께 롤백되어버리는 결과가 일어날 수 있습니다.
오늘은 성능 문제와 트랙잭션 처리에 대해 다뤄보겠습니다.
우선 이렇게 상상을 해봅시다. 메시지 전송 서비스 사용량이 폭주해서 이를 처리하는데 5초나 걸린다고 해볼게요.
동기 처리
해당 기능을 동기 처리하게 된다면(동일 스레드에서 이루어지기 때문에) 아이템 등록 서비스가 메시지 전송 서비스에 영향을 받게 됩니다. 아이템 등록은 11:45:52에 시작했지만 메시지 전송의 느린 처리 때문에 11:45:57에 응답이 내려지는 걸 볼 수 있습니다.
비동기 처리
반면 비동기 처리의 경우, 아이템 등록, 메시지 전송을 별개의 쓰레드가 수행합니다. 위에 보시면 스레드 이름이 다른 것을 확인할 수 있습니다.또한 아이템 등록이 메시지 전송 서비스에 영향을 받지 않습니다.비동기 처리를 통해 시스템간 강결합 문제를 해결할 수 있습니다.
트랜잭션 처리
시스템간 강하게 결합되어 있을 경우, 트랜잭션에 더욱 신경써야 합니다.
사실 위 소스 코드에서 @Transactional 은 필요가 없지만 트랜잭션이 필요하다고 가정해봅시다!
한 트랜잭션 내에 언체크 예외가 발생하면 트랜잭션은 롤백 처리되는데요. 특히 예제의 경우를 들어보아도 롤백 처리가 상당히 어색한 것을 알 수 있습니다. 메시지 전송이 실패하면 메뉴 등록까지 실패하게 됩니다.
과연 이게 맞을까요? 맞을수도 있지만 아닐수도 있습니다. 이렇게 시스템 강결합은 설계에 따라 원하지 않을수도 있는 트랜잭션 롤백이 일어날 수 있습니다.
이벤트 리스너를 통해 메뉴 등록과 메시지 전송 기능을 분리한다면 메뉴 등록 트랜잭션 내에 메시지 전송 기능이 존재하지 않기에 언체크 예외가 발생하더라도 트랜잭션이 롤백되는 것을 막을 수 있어요!
여담이지만 이벤트 리스너를 사용하지 않더라도 @Transactional 애노테이션의 RollbackFor 설정을 통해서도 예외에 따른 트랜잭션을 관리할 수 있습니다.