Trouble Shooting

[Spring/ 트러블 슈팅] 순환 참조(circular references) 문제

PlatinumeOlive 2024. 5. 19. 16:16

 

문제 상황

 

웹 소켓을 이용한 실시간 채팅 기능을 구현하던 중, 발생한 오류입니다. 로그를 한번 살펴보면,

  • redisSubscriber(MessageListner)를 선언하기 위해서는 webSocketConfig가 필요함
  •  webSocketConfig에서는 stompHandler를 받아서 사용함
  • stompHandler는 채팅방 정보를 알기위해 chatRoomService를 주입받음
  • chatRoomService는 채팅룸의 메시지 정보를 불러오기 위해 chatMessageService를 주입받음

이렇게 순환 종속성이 발생하였습니다.

 

우선 이것을 해결하기 위해서는 실시간 채팅 기능의 정확한 구조를 이해할 필요가 합니다. 프로젝트에서의 채팅방 요구사항은 다음과 같습니다.

 

1. 채팅방은 매칭이 생성될 시 하나만 생성된다.

2. 모든 채팅 메시지는 채팅방에 따라 DB에 저장되어야 한다.

3. 채팅 메시지를 불러 올때 DB에 걸리는 부하를 최소화 하기 위해 Redis 캐시에서 일차적으로 메시지를 불러오고, 없을 시 DB에 접근하여 불러오도록 한다.

4.  채팅방에 입장해있는 유저의 정보는 redis 캐시에 저장해두고, 불러온다.

 

대략 위의 요구사항을 충족하기 위해서 각 클래스에 역할을 따로 부여하였습니다.

 

chatMessageService 클래스는 레디스 캐시와 DB를 통해 메시지를 불러오고, 저장하는 역할을 수행합니다. redisSubscriber는 redis에서 메시지가 발행되면, 대기하고 있던 onMessage가 메시지를 받아 websocker 클라이언트에게 메시지를 전달하는 역할을 합니다. stompHandler는 presend 메서드를 통해 웹소켓에 메시지가 전송되기 전, 유저의 정보를 검증하고 채팅방의 정보를 redis에 저장하는 역할을 수행합니다. chatRoomService는 레디스와 DB 모두에 생성된 채팅방의 정보를 저장하고, 입장 정보를 저장하는 역할을 수행합니다.

 

저는 이 문제를 해결하기 위해, chatRoomService의 기능을 분리하도록 하였습니다. 현재 chatRoomService는 위에 언급한 것과 같이, chatRoom의 정보를 DB에 저장하는 역할과 Redis에서 채팅방 정보를 관리하는 역할을 수행하였습니다. 저는 이 Redis에서 수행하는 기능을 분리하여 새로운 RedisRepository 클래스를 만들었습니다.

이제 stompHandler에서 Redis에 채팅방 정보를 저장하기 위해 chatRoomService를 선언할 필요가 없이, RedisRepository 클래스만 만 사용합니다. 따라서 순환 고리에 stompHandler -> chatRoomService는 끊어지게 되었습니다.