티스토리 뷰
OverView
Friendship의 채팅 시스템은 웹소켓의 프로토콜의 일종인 STOMP 프로토콜을 사용하여 웹소켓을 통해 실시간으로 메시지를 교환합니다. Pub/Sub (Publish-Subscribe) 모델을 따르며, 이를 통해 사용자는 채팅방(chatRoom)에 메시지를 게시하고, 해당 채팅방의 메시지를 구독하는 형태를 따릅니다!
🍊 Pub/Sub 구조란?
채팅 구조를 보여드리기 전에, 앞으로 언급될 Pub/Sub 구조에 대한 이해가 필요한데요. 혹시 제 설명이 부족하다면 질문해주시면 답변 드릴 수 있도록 하겠습니다!
먼저 Pub/Sub이란 'Publisher-Subscriber', 즉 '발행자와 구독자'의 약자입니다. 이름에서 알 수 있듯이 한쪽에서 메시지를 전달하는 발행자와, 그 메시지를 받는 구독자가 있는 구조를 말하는 거죠.
이걸 이해하기 쉽게, 우리 일상에서 흔히 볼 수 있는 뉴스레터 시스템에 비유해보면 좋을 것 같아요. 뉴스레터를 작성하고 보내는 회사를 발행자로, 뉴스레터를 받아보는 우리를 구독자로 생각하면 됩니다.
그럼 여기서 '토픽'이라는 개념이 등장하는데요, 이를 뉴스레터의 '주제'라고 생각하면 됩니다. 예를 들어, '여행', '요리', '건강' 등 다양한 주제의 뉴스레터가 있을 수 있죠. 우리는 관심 있는 주제를 선택해서 그 주제의 뉴스레터만 받을 수 있습니다. 이렇게 선택한 주제가 바로 '토픽'입니다.
그럼 뉴스레터가 어떻게 우리에게 도착하는지 궁금하실 수 있어요. 이때 중요한 역할을 하는 것이 '메시지 브로커'입니다. 이를 우체국에 비유해볼게요. 발행자는 뉴스레터를 작성해서 우체국(메시지 브로커)에 보내고, 우체국은 그 뉴스레터 토픽을 구독한 우리에게 배달합니다. 우체국이 없다면(일반 웹소켓 방식) 발행자가 직접 각 구독자에게 뉴스레터를 전달해야 하겠죠? 그러나 우체국(메시지 브로커) 덕분에 발행자는 단순히 뉴스레터를 보내기만 하면 되는 구조가 됩니다!
그럼 FriendShip의 채팅은 어떤 식으로 Pub/Sub 구조를 적용할 수 있을까요?
각 채팅방의 사용자가 “채팅방”이라는 토픽을 구독하도록하고, 메시지 전송 요청 때마다 메시지(뉴스레터)를 “채팅방” 토픽에 발행하는거에요. 그럼 같은 “채팅방”을 구독하고 있는 사용자는 해당 메시지(뉴스레터)를 “메시지브로커”로부터 비동기적으로 받아볼 수 있게 되는거죠.
이 다음부터는 기본적인 메시지 전송/수신 구조부터 살펴볼 수 있도록 할게요.
메시지 전송/수신
주요 구성 요소 설명
- 사용자: 매칭된 사용자로, 메시지를 보내고 받습니다.
- 웹소켓 서버: 클라이언트의 연결 요청을 받아들이고, 메시지 라우팅을 담당합니다.
- 메시지 브로커: Pub/Sub 모델을 구현하여 메시지를 관리하고, 적절한 채팅방으로 메시지를 전달합니다.
위의 그림을 간단하게 3단계로 나누어 설명하겠습니다!
- 연결 후 구독
- 각 사용자는 STOMP 프로토콜을 통해 서버에 연결을 요청합니다.
- 연결이 성립되면, 사용자는 매칭된 채팅방 토픽(/topic/chatRoom/{chatRoomId)에 대한 구독을 요청합니다.
- 웹소켓 서버는 이 요청을 메시지 브로커로 전달하고, 구독이 승인됩니다.
- 메시지 발행
- 사용자가 메시지를 보내려면, /app/chat 엔드포인트를 통해 메시지를 서버에 전송합니다.
- 이때 데이터베이스에도 메시지를 저장합니다!
- 웹소켓 서버는 이 메시지를 받아 메시지 브로커로 전달합니다.
- 메시지 브로커는 메시지를 해당 **chatRoomId**의 토픽에 게시합니다.
- 사용자가 메시지를 보내려면, /app/chat 엔드포인트를 통해 메시지를 서버에 전송합니다.
- 메시지 수신
- 해당 채팅방을 구독 중인 각 사용자는 해당 채팅방의 메시지를 실시간으로 수신하게됩니다.
추가로 고려해야 할 점
여기까지, 기본적인 Pub/Sub 방식을 이용한 채팅 구조(전송/수신)입니다!
여기서 더 나아가, FriendShip에서는 다음 기능들이 추가로 구현되어야 합니다!
- 읽음 기능
- 메시지 불러오기
- 알림 기능
따라서 각 기능을 어떻게 구현할지 더 자세히 설명해 드리도록 하겠습니다!
읽음 기능
읽음 기능은 기존 메시지 전송시 Pub/Sub 구조와 토픽을 활용하여 다음과 같이 진행하고자 합니다.
- 읽음 확인 요청
- 사용자가 메시지를 읽으면, /app/read 엔드포인트를 통해 읽음 확인 정보를 서버에 전송합니다.
- 이 정보에는 읽은 메시지의 아이디(MessageId)가 포함됩니다.
- 웹소켓 서버 & 메시지 브로커
- 서버에서는 데이터베이스에 해당 메시지의 읽음 상태를 업데이트합니다!
- 메시지 브로커는 이 정보를 기존의 채팅방 토픽(/topic/chatRoom/{chatRoomId})에 발행합니다.
- 읽음 정보 수신
- 채팅방 토픽을 구독하고 있는 사용자는 읽음 정보를 수신합니다. 클라이언트는 수신된 메시지 아이디를 기반으로 해당 메시지를 사용자 인터페이스에서 '읽음'으로 표시합니다.
정리하자면 채팅방 입장에서부터 메시지 전송, 읽기 처리까지 다음과 같이 실시간으로 처리되어야합니다!
메시지 불러오기(Stateless)
메시지를 불러오기는 STOMP를 사용하지 않고 RESTful API 요청을 통해 이 과정을 수행하는 점이 특징인데요.
웹소켓을 사용해 실시간으로 불러올 필요가 없는 경우는 다음과 같습니다!
- 채팅방 입장시 메시지 불러오기
- 스크롤하여 이전 메시지 불러오기
해당 기능들은 단순하게 각 유저의 한 차례 자원 요청으로 이루어지므로, Stateless하게 구성하여 다음과 같은 간단한 구조로 설명 가능할 것 같습니다!
알림 기능(FCM)
비접속자의 경우 웹소켓이 연결되어있지 않아 메시지를 받기위해, 모바일 푸시 알림 서비스를 사용해야합니다.
이를 위해 FCM(Firebase Cloud Messaging)를 사용하며, 안드로이드 아이폰 모두 알림 전송이 가능해집니다.
서버에서는 메시지 전송 요청을 받을시 FCM서버로 알림 요청을 보내도록 다음과 같이 설계합니다.
- FCM 알림 전송
- 사용자 A가 **/app/chat**를 통해 서버에 메시지를 전송합니다.
- 서버는 메시지를 받고 비접속자인 사용자 B에게 FCM 알림을 전송하도록 FCM서버에 요청합니다.
- 이때 DB로부터 사용자 B의 FCM 토큰 값(기기마다 달라짐)을 찾아와 함께 요청합니다.
- FCM 서버는 사용자 B의 모바일 기기에 알림을 전달합니다.
- 알림 터치
- 사용자 B가 알림을 터치하고 채팅방에 들어갑니다.
- 사용자 B는 서버에 GET 요청을 보내어 채팅방의 최근 100개 메시지를 불러옵니다.
- 서버는 메시지 목록을 사용자 B에게 응답으로 전달합니다.
- 사용자 B의 사용자 UI에는 메시지가 불러와지며, 웹소켓 연결과 함께 새로운 메시지에 대해 읽음처리 요청을 보냅니다.
'Backend(개발)' 카테고리의 다른 글
Custom Validation 적용 (0) | 2024.06.01 |
---|---|
회원 정보를 보호합니다. (0) | 2024.06.01 |
테스트 코드를 왜 작성하는가? (with TDD) (0) | 2023.10.14 |
< git > 오류 fatal: 'origin/remote-branch-name' is not a commit and a branch 'local-branch-name' cannot be created from it (0) | 2023.07.19 |
util vs helper 무슨 차이일까? (0) | 2023.07.14 |