오늘은 프로젝트에 실시간 협업 기능을 추가하기 위해 WebSocket과 STOMP를 구현했다.
이 프로젝트에서는 여러 사용자가 동시에 작업 및 일정을 관리하기 때문에, 서버 - 클라이언트 간 양방향 통신이 필수적이다.
이번에 WebSocket을 도입한 이유는 다음과 같다.
- 실시간 일정 편집: 팀원이 캘린더에서 회의, 개인일정, 작업을 추가하거나 이동, 삭제할 때
다른 사람의 화면에서도 즉시 반영되어야 한다. - 자동 스케줄 결과 배포: AI가 최적화한 스케줄을 계산 완료 즉시,
모든 클라이언트에 결과와 세부 일정을 push해야 한다. - 충돌 경고 / 진행률 표시: 스케줄 최적화 중 충돌 감지, 진행률, 추천 변경안 같은 중간 상태를
실시간으로 띄우려면 WebSocket 스트리밍이 가장 효율적이다. - 협업 알림: 특정 작업 담당자 변경, 회의 확정, 연기 등의 알림을
팀원에게 즉시 전달하기 위해 STOMP 브로커가 필요하다. - 상세 뷰 동기화: 여러 사용자가 동일한 Task나 일정 상세 화면을 보고 있을 때,
한 사람이 메모나 상태를 수정하면 다른 사람의 화면도 즉시 갱신되어야 한다.
이러한 이유로 오늘은 WebSocket/STOMP 기반의 실시간 이벤트 시스템을 구축하였다.
오늘 진행한 작업
- WebSocket 엔드포인트 및 STOMP 브로커 설정
- 도메인 이벤트를 통합 관리하는 CollaborationEventPublisher 구현
- 작업 / 일정 / 팀 / 스케줄 최적화 서비스에 실시간 이벤트 연동
- 프론트엔드에서 STOMP 클라이언트 연결 및 구독 로직 추가
1. 서버 WebSocket/STOMP 설정
먼저 서버 쪽에 WebSocket 엔드포인트(/ws)를 열고, STOMP 브로커를 설정했다.
브로커는 /topic/** 경로를 구독용으로, /app/** 경로를 송신용으로 구분하였다.
또한 SecurityConfig에서 /ws/** 경로를 인증 예외 처리하여,
WebSocket 핸드셰이크 과정에서 401 에러가 발생하지 않도록 했다.
// SecurityConfig.java
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/ws/**", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
.anyRequest().authenticated();
}
2. CollaborationEventPublisher 구현
오늘 작업의 핵심은 도메인 이벤트를 한 곳에서 관리하는 허브를 만드는 것이었다.
CollaborationEventPublisher는 다음과 같은 역할을 수행한다.
- 작업(Task): /topic/tasks/{teamId}
- 일정(CalendarEvent): /topic/calendar/{teamId}
- 스케줄 진행률(ScheduleProgress): /topic/schedules/{teamId}
- 충돌 알림(ConflictAlert): /topic/conflicts/{teamId}
- 협업 알림(Notification): /topic/notifications/team/{teamId}, /topic/notifications/user/{userId}
- 상세 뷰 동기화: /topic/detail/{entityType}/{entityId}
이제 서비스 단에서는 단순히 eventPublisher.publishTaskEvent(message) 같은 한 줄 호출로 실시간 전파가 가능하다.
3. 서비스별 이벤트 연동
각 서비스에서 데이터 변경 직후, STOMP 메시지를 발행하도록 수정했다.
- TaskService
- 작업 생성/수정/삭제 후 실시간 브로드캐스트
- 담당자 변경 시 개인 알림 발송
- 상세 뷰(/topic/detail/task/{id}) 갱신
- CalendarEventService
- 일정 저장/수정 시 충돌 감지
- ConflictAlertMessage를 /topic/conflicts/{teamId} 로 발행
- 일정 변경 알림 + 상세 뷰 갱신
- TeamService
- 팀 생성/수정/삭제 시 팀 알림 브로드캐스트
- 초대 확정 시 팀/사용자 채널 모두 전송
- ScheduleOptimizationService
- 최적화 진행률을 /topic/schedules/{teamId} 로 스트리밍
- 완료/실패 시 팀 알림 자동 발송
4. WebSocketMessageController 추가
테스트 및 스터빙용 컨트롤러도 추가했다.
/app/** 경로로 들어온 STOMP 메시지를 동일한 /topic/** 경로로 그대로 릴레이해,
프론트에서 실시간 협업 흐름을 직접 검증할 수 있다.
5. 프론트엔드 STOMP 연동
프론트엔드에서는 공용 STOMP 클라이언트 헬퍼 (src/lib/ws.ts)를 새로 정리했다.
- SockJS 기반 연결 + 5초 자동 재연결
- JWT 토큰을 Authorization 헤더로 전송
- 개발 모드에서 STOMP 로그 출력
export function createStompClient(config?: Partial<ClientConfig>) {
const token = localStorage.getItem('accessToken');
const client = new Client({
webSocketFactory: () => new SockJS(DEFAULT_WS_URL),
reconnectDelay: 5000,
connectHeaders: token ? { Authorization: `Bearer ${token}` } : undefined,
...config
});
return client;
}
Tasks, Calendar, TeamLayout 화면에서 새로운 토픽을 구독하도록 수정했다.
- /topic/tasks/{teamId} → 작업 생성/수정/삭제 실시간 반영
- /topic/schedules/{teamId} → 최적화 진행률 업데이트
- /topic/notifications/** → 팀/사용자 알림 토스트 표시
- /topic/conflicts/{teamId} → 충돌 시 경고 토스트 표시
6. 인증 및 타입 안정화
로그인 시 사용자 ID를 공용 스토어(store/auth.ts)에 저장하고, Axios 요청 헤더에 Authorization을 자동으로 붙이도록 변경했다.
또한 @types/sockjs-client를 devDependencies에 추가해 타입 오류 없이 빌드를 완료했다.
7. 결론
오늘은 WebSocket과 STOMP를 활용해 실시간 협업 인프라를 구축했다.
CollaborationEventPublisher를 중심으로 각 서비스의 이벤트를 하나로 묶어,
작업·일정·팀 알림 등이 서버 변경 직후 바로 브라우저에 반영되도록 만들었다.
원래는 오늘 보안 파트(인증·인가·토큰 재발급) 까지 세 단계로 마무리할 계획이었지만,
소켓 구조가 생각보다 복잡하고 브로커 경로 테스트에 시간이 많이 걸려서
보안 파트는 다음 단계로 미루게 되었다..
'Project > AutoSchedule' 카테고리의 다른 글
| 9일차 - 실시간 스케줄 브로드캐스트 구현 (1) | 2025.11.11 |
|---|---|
| 8.5일차 - Spring WebSocket 보안 3단계 추가 (0) | 2025.11.11 |
| 7일차 - Swagger or Postman API 문서 작성 (0) | 2025.11.09 |
| 6일차 - Task/CalendarEvent API 검증 & 캘린더 통합 (0) | 2025.11.07 |
| 5일차 - Team / TeamMember API 기능 구현 (0) | 2025.11.05 |