[TIL 85일 차] Sprint Mission12
오늘의 성취
1. 개발 진행 상황
- 웹소켓 구현하기
WebSocketConfig구현MessageWebSocketController구현WebSocketRequiredEventListener구현
- SSE 구현하기
SseControllerGET /api/sse구현
BasicSseServiceconnect,send,broadcast,cleanUp,ping구현
SseEmitterRepositorySseEmitter객체를 메모리에 저장하는 컴포넌트
SseMessageRepository- 이벤트 유실 복원을 위해 SSE 메시지를 저장하는 컴포넌트
프로젝트 요구 사항
3. 기본 요구사항
3-01. 웹소켓 구현하기
- 웹소켓 환경 구성
spring-boot-starter-websocket의존성을 추가하세요.implementation 'org.springframework.boot:spring-boot-starter-websocket'-
웹소켓 메시지 브로커 설정
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {...}-
메모리 기반
SimpleBroker를 사용하세요.@Override public void configureMessageBroker(MessageBrokerRegistry config) {...}- SimpleBroker의 Destination Prefix는
/sub으로 설정하세요.- 클라이언트에서 메시지를 구독할 때 사용합니다.
- Application Destination Prefix는
/pub으로 설정하세요.- 클라이언트에서 메시지를 발행할 때 사용합니다.
@Override public void registerStompEndpoints(StompEndpointRegistry registry) {...}- STOMP 엔드포인트는
/ws로 설정하고,SockJS연결을 지원해야 합니다.
- SimpleBroker의 Destination Prefix는
-
- 메시지 송신
-
첨부파일이 없는 단순 텍스트 메시지인 경우 STOMP를 통해 메시지를 전송할 수 있도록 컨트롤러를 구현하세요.
@Controller public class MessageWebSocketController { ... @MessageMapping(...) }- 클라이언트는 웹소켓으로
/pub/messages엔드포인트에 메시지를 전송할 수 있어야 합니다.@MessageMapping을 활용하세요.
- 메시지 전송 요청의 페이로드 타입은
MessageCreateRequest를 그대로 활용합니다.
- 클라이언트는 웹소켓으로
-
첨부파일이 포함된 메시지는 기존의 API (
POST /api/messages)를 그대로 활용합니다.
-
- 메시지 수신
- 클라이언트는 채널 입장 시 웹소켓으로
/sub/channels.{channelId}.messages를 구독해 메시지를 수신합니다. -
이를 고려해 메시지가 생성되면 해당 엔드포인트로 메시지를 보내는 컴포넌트를 구현하세요.
@Component public class WebSocketRequiredEventListener { ... private final SimpMessagingTemplate messagingTemplate; @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void handleMessage(MessageCreatedEvent event) {...} }MessageCreatedEvent를 통해 새로운 메시지 생성 이벤트를 확인하세요.SimpMessagingTemplate를 통해 적절한 엔드포인트로 메시지를 전송하세요.
- 클라이언트는 채널 입장 시 웹소켓으로
3-02. SSE 구현하기
- SSE 환경을 구성하세요.
- 클라이언트에서 SSE 연결을 위한 엔드포인트를 구현하세요.
GET /api/sse
-
사용자별 SseEmitter 객체를 생성하고 메시지를 전송하는 컴포넌트를 구현하세요.
@Service public class SseService { public SseEmitter connect(UUID receiverId, UUID lastEventId) {...} public void send(Collection<UUID> receiverIds, String eventName, Object data) {...} public void broadcast(String eventName, Object data) {...} @Scheduled(fixedDelay = 1000 * 60 * 30) public void cleanUp() {...} private boolean ping(SseEmitter sseEmitter) {...} }connect: SseEmitter 객체를 생성합니다.send,broadcast: SseEmitter 객체를 통해 이벤트를 전송합니다.cleanUp: 주기적으로 ping을 보내서 만료된SseEmitter객체를 삭제합니다.ping: 최초 연결 또는 만료 여부를 확인하기 위한 용도로 더미 이벤트를 보냅니다.
-
SseEmitter객체를 메모리에서 저장하는 컴포넌트를 구현하세요.@Repository public class SseEmitterRepository { private final ConcurrentMap<UUID, List<SseEmitter>> data = new ConcurrentHashMap<>(); ... }ConcurrentMap: 스레드 세이프한 자료구조를 사용합니다.List<SseEmitter>: 사용자 당 N개의 연결을 허용할 수 있도록 합니다. (예: 다중 탭)
-
이벤트 유실 복원을 위해 SSE 메시지를 저장하는 컴포넌트를 구현하세요.
@Repository public class SseMessageRepository { private final ConcurrentLinkedDeque<UUID> eventIdQueue = new ConcurrentLinkedDeque<>(); private final Map<UUID, SseMessage> messages = new ConcurrentHashMap<>(); ... }- 각 메시지 별로 고유한 ID를 부여합니다.
- 클라이언트에서
LastEventId를 전송해 이벤트 유실 복원이 가능하도록 해야 합니다.
- 클라이언트에서 SSE 연결을 위한 엔드포인트를 구현하세요.
//...
GitHub Repository 주소
https://github.com/JungH200000/10-sprint-mission/tree/sprint12
Leave a comment