스포츠 앱은 예약(booking), 중고 거래(goods), 티켓팅(ticketing), 게시판(post) 등 여러 도메인을 갖고 있지만, 채팅은 message 도메인 하나로 단순 1:1(DIRECT)·그룹(GROUP) 방 생성과 REST 기반 메시지 목록 조회만 지원합니다(Room.kt, MessageDomainService.kt). 실시간 전송 계층이 없고, 동아리·모임 같은 커뮤니티 개념도 없으며, 방 외부 초대(게스트) 기능도 없습니다.
앞으로 사용자가 스포츠 동아리·모임을 만들고 그 안에서 실시간으로 소통하려면 커뮤니티 도메인과 실시간 채팅이 필요하고, 모임에 속하지 않은 지인을 특정 대화에만 임시로 참여시키는 게스트 초대도 필요합니다. 또한 채팅이 커뮤니티 전용 기능으로 고립되지 않도록, 다른 도메인(중고 거래 등)도 같은 채팅 인프라를 재사용할 수 있는 연결 구조를 이번에 함께 정의합니다.
Problem Definition
AS-IS (코드 근거)
채팅 도메인에는 실시간 전송 계층이 없다. 단 MCP 모듈이 SSE를 사용 중이라 Tomcat이 이미 장기연결에 민감하게 설정돼 있다: message 도메인(presentation·infrastructure)에는 WebSocket/STOMP 코드가 0건이라 클라이언트는 REST로 방 목록·메시지를 폴링해야 하므로 지연·배터리 소모 문제가 있습니다. 다만 채팅과 무관한 MCP(Model Context Protocol) 서버 모듈이 이미 SSE(Server-Sent Events) 트랜스포트를 사용 중입니다(application.yml:27-28, sse-endpoint: /mcp/sse, sse-message-endpoint: /mcp/message). 이 SSE 장기연결이 동기 서블릿 방식(SseEmitter)이라 연결 1개당 스레드 1개를 점유하는 문제를 이미 인지하고 Tomcat 스레드풀을 max: 400(MCP_TOMCAT_MAX_THREADS)으로 튜닝해둔 상태입니다(application.yml:4-11). 즉 새 WebSocket 연결도 같은 Tomcat 스레드풀을 공유하게 되므로 실시간 전송 계층을 “0에서 시작”하는 것이 아니라 “이미 장기연결 부하가 걸려 있는 서버에 추가”하는 문제로 다뤄야 합니다.
읽음 확인·타이핑 표시·안읽은 수 없음: RoomParticipant.kt는 joinedAt만 갖고 마지막으로 읽은 메시지 시점을 기록하지 않습니다. Room.kt도 참여자별 unread 카운트를 노출하지 않습니다.
커뮤니티 도메인 없음: domain/ 하위는 booking, common, facility, goods, mcp, message, notification, operator, payment, post, ticketing, user, weather로 구성되어 있고 동아리·모임을 표현하는 도메인이 존재하지 않습니다. 모임을 만들고 그 모임 전용 채팅을 연결할 방법이 없습니다.
게스트 초대 없음: 관련 코드가 0건입니다. MessageDomainService.kt#joinRoom은 임의 userId를 즉시 참여자로 추가할 뿐 초대·수락·권한 제한·만료 개념이 없습니다.
Room이 외부 엔티티에 연결되지 않음: Room 엔티티는 type(DIRECT/GROUP)과 name만 갖고 있어 특정 커뮤니티·주문·예약 같은 외부 컨텍스트와 구조적으로 연결할 필드가 없습니다. 향후 각 도메인이 채팅이 필요할 때마다 자체 방식을 중복 구현할 위험이 있습니다.
TO-BE
WebSocket/STOMP 기반 실시간 송수신, 읽음 확인, 타이핑 표시, 안읽은 수 배지를 갖춘 채팅으로 고도화합니다.
커뮤니티(동아리·모임) 도메인을 신설해 개설·가입·멤버십·역할을 지원하고, 커뮤니티 전용 그룹 채팅과 자동 연동합니다.
정회원을 특정 방에 한시적으로 초대하는 게스트 모델을 도입해 권한이 제한된 임시 참여를 지원합니다.
Room에 contextType/contextId를 도입해 외부 도메인이 채팅을 재사용할 수 있는 일반 확장 메커니즘을 만들고, 커뮤니티 그룹 채팅을 첫 번째 연동 사례로, goods 거래 채팅을 두 번째 연동 사례로 확정합니다.
Goals / Non-Goals
Goals
WebSocket/STOMP 실시간 송수신, 읽음 확인, 타이핑 표시, 안읽은 수 배지 (P0)
커뮤니티(동아리·모임) 개설·가입·멤버십·역할(방장/멤버)과 커뮤니티 전용 그룹 채팅 자동 연동 (P0)
게스트 초대: 정회원을 특정 방에 한시적으로 초대, 발화 권한 제한, 참여 기간 만료, 초대·수락·거절 흐름 (P0)
Room-외부엔티티 연결 메커니즘(contextType/contextId) 설계, 1번째 연동(커뮤니티) + 2번째 연동(goods 거래 채팅) 확정 (P0/P1)
Non-Goals
비회원 토큰/링크 기반 게스트 접근 — 게스트는 가입된 userId를 가진 정회원으로 한정합니다. 비회원 초대는 이번 범위 밖입니다.
커뮤니티 피드/게시판/이벤트 관리 — 커뮤니티는 멤버십·역할·전용 채팅방까지만 다루고, post 도메인과의 결합(모임 게시판)이나 이벤트(일정) 관리는 향후 별도 PRD로 다룹니다.
음성/영상 통화 — 텍스트 메시지 실시간 전송만 다룹니다.
미디어(이미지·파일) 첨부 — 텍스트 메시지만 지원합니다. 다음 단계에서 별도 검토합니다.
메시지 전문 검색(full-text search) — 커서 기반 목록 조회만 유지합니다.
booking·ticketing 실제 채팅 연동 — 확장 메커니즘은 범용으로 설계하지만, 이번 PRD에서 FR로 구체화하는 도메인 연동은 커뮤니티(1번째)와 goods(2번째) 2건으로 한정합니다. booking·ticketing 연동은 향후 별도 PRD입니다.
다중 디바이스 동시 접속 정교화 — 여러 기기에서 동시 로그인 시 읽음 상태는 단순 최종 갱신(last-write-wins)으로 처리하고, 기기 간 정교한 동기화는 다루지 않습니다.
User Scenarios
페르소나: 방장(Host, 커뮤니티 개설자), 멤버(Member, 커뮤니티 가입자), 게스트(Guest, 특정 방에 한시 초대된 정회원), 거래 당사자(Trader, goods 구매자/판매자)
(해피 패스) 방장이 “주말 축구 모임” 커뮤니티를 만들면 전용 그룹 채팅방이 자동 생성되고, 멤버를 초대하면 멤버가 채팅방에 자동 참여합니다.
(해피 패스) 멤버가 앱을 재실행하면 채팅방 목록에서 안읽은 메시지 수 배지와 최근 메시지 미리보기를 확인합니다.
(해피 패스) 방장이 커뮤니티 비멤버인 지인을 특정 채팅방에 게스트로 초대하고, 게스트가 초대를 수락해 대화에 참여하고 발화합니다.
(예외) 게스트가 초대를 거절하면 초대가 취소되고 방에 참여하지 않습니다.
(상태 보호) 게스트의 참여 기간이 만료되면 자동으로 방에서 방출되고, 이후 메시지 열람·발화가 차단됩니다.
(해피 패스) 구매자가 중고 상품 상세 화면에서 “채팅하기”를 누르면 판매자와의 1:1 거래 채팅방이 자동 생성되거나 기존 방으로 이동합니다.
(예외) WebSocket 연결이 끊긴 상태에서 메시지를 보내면 클라이언트가 재연결을 시도하고, 재연결 성공 시 끊긴 구간의 메시지를 backfill로 채웁니다.
(상태 보호) 멤버가 커뮤니티를 탈퇴하거나 방장에게 강퇴되면 연결된 그룹 채팅방에서도 자동으로 퇴장됩니다.
(빈 상태) 참여 중인 채팅방이 없는 신규 가입자는 채팅 목록에서 빈 상태 화면을 봅니다.
Benchmarking
제품명
카테고리
참조 패턴
URL
당근마켓
중고거래/지역 커뮤니티 앱
채팅을 별도 서비스로 분리하고 WebSocket 연결 사용자는 FCM 지연과 무관하게 실시간 이벤트를 즉시 수신하도록 이중화. 중고거래·직거래 등 “거래 목적 채팅” 구조를 하나로 통일해 여러 도메인이 재사용 — 이번 PRD의 contextType/contextId 확장 메커니즘과 동일한 문제의식
Single-Channel Guest(채널 1개만 접근)와 Multi-Channel Guest를 구분하고, 게스트의 메시지 발송·채널 생성·파일 공유를 관리자가 제한하며, 일정 기간 후 자동 비활성화(만료)하는 게스트 모델 — 이번 PRD의 FR-13(권한 제한)·FR-14(만료)의 직접 참조 대상
이름·설명·공개 여부(공개/비공개)·스포츠 종목 카테고리를 지정해 커뮤니티를 개설할 수 있다
FR-2
P0
공개 커뮤니티는 즉시 가입, 비공개 커뮤니티는 방장 승인 후 가입된다
FR-3
P0
커뮤니티 멤버는 방장(Host, 1명) 또는 멤버(Member, N명) 역할을 가지며, 방장은 멤버를 강퇴하거나 방장 권한을 위임할 수 있다
FR-4
P0
커뮤니티 개설 시 전용 그룹 채팅방이 자동 생성되고, 커뮤니티 멤버는 가입과 동시에 해당 채팅방 참여자로 자동 등록된다
FR-5
P0
커뮤니티 탈퇴 또는 강퇴 시 연결된 그룹 채팅방에서도 자동으로 퇴장 처리된다
실시간 전송
ID
우선순위
요구사항
FR-6
P0
WebSocket/STOMP 연결을 통해 메시지를 실시간으로 송수신한다
FR-7
P0
참여자별 마지막으로 읽은 메시지 시점을 추적해 상대방에게 읽음 여부를 표시한다
FR-8
P0
상대가 입력 중임을 실시간으로 표시하고, 입력이 멈추면 수 초 내 자동으로 표시가 사라진다
FR-9
P0
채팅방 목록과 전체 배지에 참여자별 안읽은 메시지 수를 노출한다
FR-10
P1
WebSocket 연결이 끊기면 REST로 폴백하고, 재연결 성공 시 끊긴 구간의 메시지를 backfill로 채운다
게스트 초대
ID
우선순위
요구사항
FR-11
P0
방장은 특정 채팅방에 커뮤니티 비멤버인 정회원(userId 보유)을 게스트로 초대할 수 있다
FR-12
P0
게스트 초대는 수락/거절 흐름을 가지며, 수락 시 참여자로 추가되고 거절 시 초대가 취소된다
FR-13
P0
게스트는 발화 가능 여부를 초대 시 설정할 수 있고(읽기 전용 또는 발화 가능), 초대된 방 외의 해당 커뮤니티 컨텍스트 기능(멤버 목록 조회 등 커뮤니티 멤버십 범위의 기능)에는 접근할 수 없다
FR-14
P0
게스트 초대 시 참여 만료 시각(예: 7일)을 설정하며, 만료 도달 시 자동으로 방에서 방출되고 그 시점까지의 읽은 이력은 유지된다
FR-15
P1
방장은 만료 전에도 게스트를 수동으로 방출할 수 있다
FR-13 용어 정의: “게스트”는 전역 계정 롤이 아닙니다. domain/common/UserRoleName.kt에는 USER/ADMIN/FACILITY_OWNER/EVENT_HOST/GOODS_SELLER/OPERATIONS_MANAGER만 존재하고 GUEST 롤이 없습니다. 게스트는 정회원(UserRoleName=USER 등 기존 롤 유지)이 특정 방(또는 그 방이 속한 커뮤니티)에 대해 갖는 참여 스코프 속성이며, 그 컨텍스트 밖에서는 일반 정회원과 동일한 권한을 갖습니다.
확장 메커니즘
ID
우선순위
요구사항
FR-16
P0
Room이 contextType/contextId로 외부 도메인 엔티티와 연결될 수 있는 일반 확장 메커니즘을 제공한다
FR-17
P0
커뮤니티 그룹 채팅을 위 메커니즘의 1번째 연동 사례로 구현한다(contextType=COMMUNITY)
FR-18
P1
중고 상품 상세 화면의 “채팅하기”에서 구매자와 판매자(Product.ownerId) 간 1:1 거래 채팅방을 2번째 연동 사례로 생성/조회한다(contextType=GOODS_PRODUCT)
FR-18 도메인 선정 근거: Product.kt가 ownerId(개인 판매자)를 명시적으로 보유해 구매자-판매자 1:1 관계가 명확합니다(domain/goods/entity/Product.kt#requireOwnedBy). 반면 Booking.kt는 userId(예약자)와 slotId(시설 슬롯)만 가지며 대화 상대가 될 개인(시설 운영자 개인 계정)이 도메인 모델상 명확하지 않고, ticketing도 이벤트 주최자 대비 구매자 구도가 즉시 채팅으로 이어지지 않습니다. 따라서 goods가 확장 메커니즘의 2번째 연동으로 가장 자연스럽습니다.
기존 데이터 호환 설계 결정: contextType/contextId는 nullable로 추가합니다. 기존 DIRECT/GROUP 방(개인 1:1·순수 그룹 채팅)은 특정 외부 컨텍스트에 속하지 않으므로 두 컬럼을 null로 유지하고, 커뮤니티·goods 연동 방만 값을 채웁니다. Room/RoomParticipant에 컬럼을 추가하는 것은 additive 변경이라 기존 스키마·데이터와 충돌하지 않습니다.
Non-Functional Requirements
항목
목표 수치
비고
동시 WebSocket 세션
로컬 docker 단일 인스턴스 기준 300세션 처리
개인 프로젝트 규모 가정 초안 — Open Questions 참조
기존 MCP SSE 연결과 스레드풀 공유 영향
신규 WebSocket 연결이 MCP SSE(application.yml:27-28)와 동일 Tomcat 인스턴스·스레드풀(max: 400, application.yml:4-11)을 공유한다는 전제로 설계·용량 산정
WebSocket 구현 방식(동기 STOMP vs 비동기)에 따라 연결당 스레드 점유 여부가 달라지므로 TDD 단계에서 SSE 최대 연결 수 + WebSocket 목표 세션 수(300) 합산 기준 스레드풀 재산정 필요
메시지 전달 지연
발신~수신 P95 500ms 이내
동일 로컬 환경 기준
읽음 확인 반영 지연
P95 1초 이내
—
재연결 정책
지수 백오프로 재시도, 3회 연속 실패 시 REST 폴링으로 전환
FR-10
인증
WebSocket 연결 시에도 REST와 동일한 JWT 인증 필수
미인증 연결 거부
게스트 접근 통제
참여자 검증(기존 NotRoomParticipantException 계열) + 만료 시각 검사를 매 요청마다 수행
FR-14
메시지 보존
기존 소프트 삭제 정책 유지, 별도 만료 삭제 없음(무기한 보존)
스토리지 증가 대응 필요 여부는 Open Questions
Operations
지표: WebSocket 활성 연결 수, 초당 발송 메시지 수, 메시지 발신~수신 지연 P95/P99, 읽음 확인 반영 지연, 게스트 만료 배치 성공률, 커뮤니티별 활성 채팅방 수
알림: WebSocket 연결 실패율이 5%를 초과하면 알림, 게스트 만료 배치가 실패하면 알림(기존 infrastructure/notification/gateway 채널 재사용)
대시보드/로그: 커뮤니티별 활성 채팅방 수와 게스트 초대 수락률을 주기적으로 집계해 노출
Success Metrics
지표
목표
측정 방법
커뮤니티 그룹 채팅 D7 활성화율
커뮤니티 가입 후 7일 내 채팅방 메시지 발송 비율 40% 이상
가입 이벤트와 메시지 발송 이벤트 조인 집계
메시지 전달 지연 목표 달성률
발신~수신 P95 500ms 이내 요청 비율 99% 이상
클라이언트-서버 타임스탬프 로그 집계
게스트 초대 수락률
60% 이상
초대 발송 대비 수락 건수
goods 거래 채팅 전환율
1단계는 목표치 없이 측정 전용으로 운영 — 2단계(FR-18 배포) 후 4주간 베이스라인 수집해 목표치 확정
채팅방 생성(contextType=GOODS_PRODUCT) 대비 GoodsOrder 완료 건수
Milestones
단계
범위
1단계
커뮤니티 도메인(FR-15) + 실시간 전송 핵심(FR-69) + 게스트 초대 핵심(FR-1114) + 확장 메커니즘과 커뮤니티 연동(FR-1617)
2단계
WebSocket 재연결 backfill(FR-10), 게스트 수동 방출(FR-15), goods 거래 채팅 연동(FR-18)
3단계 (향후, 범위 밖)
booking·ticketing 연동, 미디어 첨부, 메시지 전문 검색
Open Questions
게스트 초대 발신 권한을 방장으로만 한정할지, 일반 멤버도 초대할 수 있게 할지 — 초안은 방장만 가능으로 가정(Slack 게스트 초대 기본 정책 참조)
동시 접속 300세션·P95 500ms는 실측 이전 추정치입니다. 실제 운영 데이터 확보 후 재조정이 필요합니다.
메시지를 무기한 보존하는 정책이 스토리지 증가에 따라 유지 가능한지 별도 검토가 필요합니다.
게스트가 여러 기기에서 동시 로그인할 경우의 읽음 상태 동기화는 이번 범위에서 제외했으나, 실사용 시 문제가 되면 재검토가 필요합니다.
Document History
날짜
변경 내용
2026-07-04
최초 작성 — Q1~Q4 확정 답변 반영(실시간 전송 P0 포함, 정회원 게스트 모델, 커뮤니티 도메인 신설, contextType 확장 메커니즘 + 커뮤니티·goods 2건 연동)
2026-07-04
리뷰 반영(NEEDS_REVISION) — ① AS-IS를 “MCP 모듈이 이미 SSE 사용 중이며 Tomcat 스레드풀을 장기연결 대응으로 튜닝해둔 상태” 사실로 정정(application.yml:4-11,27-28 근거), ② NFR에 “기존 MCP SSE 연결과 스레드풀 공유 영향” 행 추가, ③ FR-13을 전역 롤이 아닌 커뮤니티/방 스코프 참여 속성 기준으로 재기술하고 UserRoleName.kt에 GUEST 롤이 없음을 근거로 용어 정의 각주 추가, ④ Success Metrics의 goods 거래 채팅 전환율을 “1단계 측정 전용, 2단계 이후 베이스라인 확정”으로 명시, ⑤ contextType/contextId nullable 유지(기존 방 호환) 설계 결정 각주 추가