배경
ClassMate는 코치-코치이 Q&A, 커뮤니티(게시글·콘텐츠·설문), 컨퍼런스 3 도메인이 한 앱에 공존하는 LMS 제품이었습니다. 알림 UI · 상태 · 네비게이션이 서비스별로 흩어져 있었고, 토스트는 큐잉 없이 즉시 사라지거나 클릭해도 정확한 위치(어느 도메인, 어느 콘텐츠, 어느 페이지)로 이동하지 못하는 이슈가 누적되고 있었습니다. 웹과 모바일 동시 운용 중이라 클릭 한 번에 라우터 · 서브메뉴 · 리스트 선택까지 한꺼번에 회복돼야 했습니다.
접근
알림 페이로드를 { service, service_id, payload } 단일 정규화 스키마로 통일했습니다. Map 기반 ID 머지 리듀서로 서버 페치와 실시간 푸시를 idempotent하게 통합하고, eventManager 채널로 토스트 · 팝업 · 리스트가 모두 같은 store를 구독하게 했습니다.
핵심은 클릭 한 번에 모든 게 회복되는 단일 콜백이었습니다. 도메인별 select 액션 + 페이지네이션 이벤트 + 서브메뉴 dispatch가 한 함수 안에서 service 분기 5종(qna / post / contents / survey / conference)으로 합성되도록 라우터를 구성했습니다.
알림은 원래 화면 상태로 사용자를 돌려보내는 일입니다. 라우터 이동만으로는 부족합니다. 어느 페이지 · 어느 서브메뉴 · 어느 리스트 항목 · 어느 페이지네이션 위치 모두가 같이 회복돼야 알림이 진짜 동작합니다.
토스트는 큐잉이 필요했습니다. Map 기반 store에 두고 미들웨어가 3초 타이머로 자동 dequeue. 팝업은 활성 시 미열람 알림을 일괄 read 처리하고, 외부 클릭 감지로 자동 dispose. 리스트는 미열람만 페이지네이션 — 모든 채널이 같은 store에서 파생되니 read 상태가 어디서 변경돼도 다른 채널이 즉시 따라옵니다.
결과
- 3 도메인 × 3 채널 = 9개 노출 지점을 단일 컨테이너 1쌍(
Alarm/ToastAlarm)으로 통합. - 알림 클릭 진입 분기 5종을 단일 콜백에서 합성.
- 서버 페치 + 실시간 푸시를 Map 머지 + 시간 역정렬로 idempotent 결합 — 중복·순서 회귀 표면 0.
- 토스트 큐 throttle 500ms + 자동 dequeue 3초, 페이지 사이즈 20 무한 스크롤.
- 12주 동안 17 커밋, 팀 안에서 알림 영역 담당 — 백엔드는 별도 팀원, 다른 화면 영역은 동료들과 공동 개발.
배운 점
알림 같은 cross-domain 통합 영역에서 가장 큰 깨달음은 상태와 라우팅을 같은 evbus에 묶어야 한다는 점이었습니다. 알림 클릭 → 페이지 전환 → 서브메뉴 활성 → 리스트 선택 → 페이지네이션 복원이 모두 같은 콜백에서 일어나지 않으면 어디 한 단계가 후행적으로 따라오면서 깜빡임이나 잘못된 위치 노출이 발생합니다. 모든 채널이 같은 store를 구독하게 만들면, 클릭 시점에 상태가 한 번 변경되고 UI가 자연스럽게 정렬됩니다.
또 한 가지: 서버 페치와 실시간 푸시를 같은 코드 경로에 흡수하는 머지 리듀서. 새 알림이 WebSocket으로 도착하든 페이지네이션으로 도착하든, Map 키 기반 idempotent 머지 한 곳에서 처리하면 두 입력 채널의 동시 도착 race도 자연히 닫힙니다.