수많은 우여곡절 끝에 배포된 서버에 채팅 기능을 적용하게 되었다!
간단히 말하자면 원래 담당했던 백엔드 팀원이 던져놓은 빅 똥을 치우게 되었다.
내 프로젝트의 스택은 아래와 같다.
Java 17 / Spring 3.x / JPA / Redis / Spring Security / JWT / OAuth / Websocket / MySQL / Firebase
그리고 처음 팀원이 던져놨던 채팅 구현을 위한 기능은 Kafka + Socket.io 였다. 내가 알기로 Kafka는 MSA구조에서 그룹으로 묶어 메세지를 전달하는 메세지 큐 방식에 적합한 절대 가볍지 않은 스펙의 프레임워크였으며, Socket.io 또한 Spring으로 만든 프로젝트가 아닌 별도로 서버가 생성되는 과정을 요구하는 방식이였으나 팀원을 믿고 기다렸었다.
하지만... 결국 팀원은 중도하차하게 되었고 채팅과 알람을 내가 맡게 되었다.
변경점은 아래와 같고 앞으로 이야기 하게 될 내용들은 아래 기술위주로 진행되었다.
Redis, Websocket, Firebase
1. Redis의 Pub/Sub 방식의 사용
가장 접하기 쉽고 자주 만져봤던 Redis를 사용하였고 Pub/Sub 방식으로 구현할 예정이었다.
Pub/Sub에 대한 개념은 아래 그림으로도 충분히 이해 가능할것이다.
Topic(신문)을 구독한 사람이 Publisher(출판사)에서 Topic을 던져주면 Subscriber(구독자)들이 내용을 읽을 수 있게 만드는 것이다.
이론은 항상 쉽지만 막상 만들려고 하면 매우 막막한 주제였다.
2. WebSocket과 STOMP
이 두 가지는 Spring에서 많은 지원을 해주는 기능이다. 아래 링크를 통해 공식문서 내용을 확인할 수 있다.
https://docs.spring.io/spring-framework/reference/web/websocket/stomp.html
물론 이걸 다 읽기는 매우 귀찮고 시간이 촉박한 분들이 있을 수 있으니 핵심기능을 간략히 설명하자면 아래와 같다.
프로젝트에서 구현한다는 건 물론 면접에서 물어볼 수도 있는 부분이기에 한 번 알아두면 좋을 것이다.
1. Webosocket 프로토콜 지원
클라이언트와 서버가 실시간으로 양방향 통신(전이중 통신)을 할 수 있는 소켓을 연결시켜준다. 기존의 HTTP 통신은 계속해서 연결(stateful)이 아닌 무상태성(stateless) 방식의 연결이다. 이 방식은 다양한 전략이 있으나 내가 아는건 Polling / Long Polling / Streaming정도이다. 이 개념들을 설명하면 글이 길어지니 별도로 기록해두겠다.
스프링은 Websocket을 지원하지 않는 환경에서는 SockJS를 통해 Fallback 옵션을 제공한다.
2. STOMP 프로토콜 지원
STOMP(Simple Text Oriented Messaging Protocol)은 Websocket위에 동작하며, Spring에서는 이를 구현하여 메세지 기능을 제공한다. 그 기능으로서 첫 번째는 라우팅 기술이다. @MessageMapping으로 라우팅된 메세지를 처리하고, 특정 목적지로 메세지를 보낸다. 두 번째는 주제 및 큐 지원이다. STOMP의 목적지 패턴을 통해 topic(BroadCasting)과 queue(1:1메시징)를 모두 지원한다.
3. 메세지 브로커 구성
내장 브로커를 통해 Web Socket 세션 내에서 간단한 목적지 간의 메세지 전달을 처리한다.
외장 브로커를 통해 RabbitMQ와 같은 외부 메세지 브로커를 사용하여 확장가능하고 신뢰성 있는 메세지를 전달한다.
4. SockJS폴백 지원
HTTP기반 대체 통신으로 AJAX Long Polling, XHR Streaming, Server-Sent Events(SSE) 등을 사용하여 WebSocket이 지원되지 않는 환경에서도 동작할 수 있게 해준다.
클라이언트 호환성 확대로 구형 브라우저에서도 Websocket기반 메세징 기능을 제공한다.
5. 메세지 처리 핸들러
@MessageMapping으로 특정 목적지에 도달한 메세지를 처리한다.
@SendTo 처리된 결과를 특정 주제나 목적지로 브로드캐스트 한다.
@SubScribeMapping으로 클라이언트가 특정 주제를 구독했을 때 초기 데이터를 제공한다.
6. WebSocket 세션 관리
연결 이벤트 리스너 SessionConnectEvent, SessionDisconnectEvent를 통해 연결 상태를 추적할 수 있다.
7. 권한 통합
Spring Security와 통합하여 Handshake인증 , 목적지 기반 권한 부여가 가능하다.
8. 클라이언트 라이브러리 호환성
STOMP.js를 사용하여 브라우저에서 STOMP프로토콜 사용이 가능하다.
브라우저에서 Sock JS 폴백 옵션을 활용할 수 있다.
3. 의존성 주입
일부 의존성만 보여주면 환경구성에 문제가 생길 수 있기 때문에 중요한 정보들을 제외하고는 전체 코드를 공개하려고 한다.
yml파일은 websocket관련 설정이 없기 때문에 넘어가겠다.
여기서 사용한 Firebase는 유저가 미접속 상태일때 채팅이 전송되는 경우 알림을 생성해내기 위한 용도로 사용되었다.
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '3.3.0'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
//jwt
implementation 'org.json:json:20231013'
implementation 'com.auth0:java-jwt:4.2.1'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
//mapstruct
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
//swagger ui
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.1.0'
testImplementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-api', version: '2.1.0'
//Json Parsing
implementation 'com.fasterxml.jackson.core:jackson-databind'
//aws s3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
//AOP
implementation 'org.springframework.boot:spring-boot-starter-aop'
//fcm
implementation 'com.google.firebase:firebase-admin:9.2.0'
//web socket
implementation 'org.springframework.boot:spring-boot-starter-websocket'
다음 포스팅에서는 Websocket과 Redis Config파일 설정을 알아보겠다.
'Redis > Redis 채팅' 카테고리의 다른 글
Redis Pub/Sub을 활용한 채팅 구현의 여정 - Service 레이어 (0) | 2024.12.19 |
---|---|
Redis Pub/Sub을 활용한 채팅 구현의 여정 - Chat Domain(Entity ~ Controller) (0) | 2024.12.08 |
Redis Pub/Sub을 활용한 채팅 구현의 여정 - 환경설정 (0) | 2024.11.29 |
[Redis] SpringBoot + Redis Pub/Sub 으로 채팅 구현 하기 (0) | 2024.07.18 |