관심상점 및 관심업종 구독 모델 통합
ADR-180 관심상점 및 관심업종 구독 모델 통합
1. Metadata
- ADR ID: ADR-180
- Status: draft
- Date: 2026-03-11
- Owner: YSY
2. Context
- 관심상점/관심업종은 개인화 신호이지만, 사용자가 보는 슬롯 리스트의 필터와 충돌할 수 있다.
- 관심업종 알림은 유용성이 크지만, 이벤트 밀도가 높은 업종에서 푸시 폭주 리스크가 있다.
- 슬롯 카드는 최소 정보 전달이 원칙이므로 관심상점 표시는 밀도를 해치지 않는 수준으로 제한해야 한다.
3. Domain Decision
- 관심상점은
user_favorite_store 도메인 모델로 관리한다. - 관심업종은
user_interest_category 도메인 모델로 관리하며 category_id는 ADR-005 canonical 업종 코드 체계를 따른다. user_favorite_store는 (user_id, store_id) 유니크 제약을 강제한다.user_interest_category는 (user_id, category_id) 유니크 제약을 강제한다.- 관심 구독 데이터는 알림/추천 입력으로 사용하되, 슬롯 노출/수량/상태 전이의 필수 조건으로 사용하지 않는다.
- 구독 등록/해제 요청은 멱등 처리한다.
- 슬롯 응답은 사용자 컨텍스트가 있을 때
is_favorite_store 판별 결과를 포함한다. - 관심업종은
강제 노출 조건이 아니라 후순위 랭킹/알림 신호로 정의한다. - 관심업종은 데이터 모델은 유지하되, 기본 운영 상태는
보류(비활성)로 둔다.
4. Product Decision
- 관심상점/관심업종 구독은 사용자 선택 기반(옵트인)으로 운영한다.
- 구독 알림은 빈도 상한과 수신 거부 경로를 기본 정책으로 제공한다.
- 구독 데이터는 개인화 노출/알림의 보조 신호로만 사용하고, 예약 성공 보장을 암시하지 않는다.
- 구독 대상 삭제/비활성 시 해당 구독은 자동 비활성 처리한다.
- 슬롯 리스트에서는 관심상점에만
채워진 별 마크를 노출하고 비관심 슬롯에는 별 마크를 노출하지 않는다. - 관심업종 등록만으로 현재 적용된 검색 필터를 우회해 강제 노출하지 않는다.
- 노출 우선순위는
사용자 명시 필터 > 정책/가용성 조건 > 관심신호 기반 랭킹으로 고정한다. - 기본 정렬은 기존 슬롯 정렬(시간/거리/가용성)을 유지하고, 관심신호는 소폭 가중치(soft boost)만 허용한다.
관심매장 우선은 기본값이 아닌 사용자 선택 정렬 옵션으로만 제공한다.- 1단계 운영 범위는
관심매장으로 제한하고, 관심업종 사용자 기능은 보류한다. - 관심매장 알림은
즉시 알림을 허용한다. - 보류 중인 관심업종은 UI 진입/알림 발송을 기본 비활성으로 유지한다.
- 관심업종 재개는 전환율, 수신거부율, 알림 볼륨 지표가 목표 범위를 충족할 때만 결정한다.
- 관심매장 등록의 1차 목적은 재방문 매장의 재예약 시간 단축과 신뢰 매장 재발견 비용 절감으로 정의한다.
- 1단계 관심매장 등록 기본 경로는
슬롯 탭 -> 상세 화면으로 고정하고 스와이프 액션은 포함하지 않는다. - 보조 경로는 기본 등록 흐름을 대체하지 않는 범위에서 허용하며, 저장 탭의
최근 본 항목에서 관심매장 추가와 같은 명시적 보조 액션을 제공할 수 있다. - 파트너 화면에는
내 매장을 관심매장으로 등록한 사용자 수를 노출한다. - 파트너용 관심등록 수는 실시간 증감이 아닌 시간 단위(1시간) 집계값으로 제공한다.
관심매장과 관심매장 알림은 동일 개념으로 취급하지 않고, 저장/재방문 신호와 알림 발송 권한을 구분된 제품 상태로 다룬다.- 사용자가 알림을 꺼도
관심매장 자체의 효용은 유지되며, 최소 효용은 저장 탭 재진입, 탐색 재노출, 관심매장 우선 정렬 후보, 신뢰 매장 표식으로 정의한다. - 1단계 관심매장 알림은 복잡한 사용자별 세부 필터보다
전역 on/off + 서버 측 빈도 제한을 우선 적용한다. - 사용자별 알림 필터는 필요성이 확인될 경우
즉시/요약, 주중/주말 같은 소수 옵션부터 단계적으로 도입하고, 초기부터 상세 요일/시간/매장별 조건 편집을 기본 제공하지 않는다. - 탐색 카드의 기본 표식은 계속
관심매장(별) 중심으로 유지하고, 알림 상태는 관심매장 맥락이 명확한 화면에서만 추가 표현할 수 있다. - 관심매장 목록 또는 관심매장 맥락에서 다시 노출되는 슬롯에는
알림 상태를 표현하는 아이콘을 둘 수 있다.
5. UX Decision
- 사용자가 관심상점을 등록/해제할 수 있는 관리 화면을 제공한다.
- 슬롯 카드의 관심상점 마크는 매장명 행 오른쪽 끝(헤더 우측)에 고정 배치한다.
- 슬롯 리스트의 마크는 상태 인지용으로 사용하고, 기본 등록/해제 액션은 상세 화면에서 수행한다.
- 토글은 낙관적 업데이트를 적용하되 실패 시 즉시 롤백하고 오류 토스트를 노출한다.
- 비로그인 상태에서 토글 시 로그인 인터셉트를 호출하고, 로그인 후 원래 액션을 1회 재시도한다.
- 구독 해제는 1탭으로 처리하고 되돌리기 경로를 제공한다.
- 구독은 알림 보조 기능임을 명시하고 예약 확정 보장으로 오인되지 않게 안내한다.
- 관심업종 메뉴/토글은 보류 상태에서 사용자에게 노출하지 않는다.
- 저장 탭의
최근 본 항목은 보조 경로로만 관심매장 등록을 지원할 수 있으며, 카드 내부의 명시적 관심매장 추가 액션을 제공할 수 있다. - 이 보조 액션은
최근 본의 원래 목적(상세 재진입)을 해치지 않도록 카드 기본 탭 동작과 명확히 분리되어야 한다. - 보조 액션은 제스처 학습을 요구하지 않는 명시적 버튼 또는 동등한 수준의 직접 조작으로 제공한다.
- 보조 액션 성공 시 즉시 관심매장 목록에 반영하고, 이미 등록된 매장/로그인 필요/실패 상태는 인라인 피드백으로 구분해 안내한다.
- 저장 탭 보조 액션의 상세 문구/접근성 규칙은 본 ADR에서 확정하지 않고 별도 문서에서 정의한다.
관심매장 토글과 알림 상태는 UI에서 분리된 의미로 다루고, 사용자가 알림을 끄더라도 별표(저장 상태)는 유지되어야 한다.- 탐색 메인 슬롯 카드에는 기본적으로 별표만 유지하고, 종 아이콘까지 함께 올려 카드 의미를 과도하게 늘리지 않는다.
알림 아이콘은 저장 탭의 관심매장 목록, 관심매장 섹션, 또는 관심매장으로 재맥락화된 슬롯 목록처럼 이 슬롯이 왜 여기 보이는지가 명확한 문맥에서만 노출한다.- 알림 품질 제어는 초기부터 상세 필터 UI를 크게 노출하기보다 서버 측 중복 억제, 발송 빈도 제한, 전역 on/off를 우선 적용한다.
- 사용자별 세부 필터 UI가 필요해지면
즉시/요약, 주중/주말 순으로 도입을 검토하고, 상세 요일/시간대 편집은 후순위로 둔다. - 관심매장 맥락에서 노출되는 알림 아이콘은 상태 인지용으로 사용하고, 필요 시 해당 매장의 알림 on/off 또는 알림 관리 화면 진입점으로 연결할 수 있다.
6. Tech Decision
- 관심상점/관심업종 저장 API는 멱등 업서트(upsert)로 구현한다.
- 중복 삽입 방지는
(user_id, store_id), (user_id, category_id) 유니크 인덱스로 보장한다. - 슬롯 조회 API는 사용자별
is_favorite_store:boolean을 계산해 반환한다. - 관심상점 토글 API는
등록 upsert와 해제 delete 모두 멱등 처리한다. - 구독 변경 이벤트는 감사로그와 알림 파이프라인 입력 이벤트로 분리 기록한다.
- 알림 후보 생성은 폴링 기반 클라이언트 감시가 아니라 서버 이벤트 트리거 + 비동기 큐로 처리한다.
- 관심업종 푸시는 사용자별 일/시간 상한, 업종별 쿨다운, 동일 매장 중복 제거 키를 적용한다.
- 이벤트 누락 복구를 위해 저빈도 재조정 배치를 운영하되 실시간 판단 소스는 이벤트 파이프라인으로 고정한다.
- 푸시 발송 대상 단말은
user_push_tokens를 SoT로 관리하고, 관심매장 알림 큐는 사용자 단위 레코드로 적재한다. - 관심매장 슬롯 오픈 알림은
slots 발행 이벤트에서 user_notification_queue에 적재한 뒤, 서버 워커/함수에서 실제 푸시 발송을 수행한다. - 관심업종 사용자 기능 플래그는 기본
off로 유지하고, 운영 승인 시에만 on 전환한다. - 관심업종 기능이
off인 환경에서는 관련 알림 후보를 생성하지 않는다. - 슬롯 목록 조회는 선택 정렬 값으로
favorite_first를 받을 수 있으나 기본값은 기존 정렬을 유지한다. user_favorite_store를 SoT로 두고 partners.favorite_user_count는 1시간 주기 재계산 캐시로 관리한다.- 관심매장 등록/해제 시 카운트 컬럼을 즉시 증감하지 않고 재집계 배치에서만 반영한다.
7. Ops Decision
- 관심구독 알림 실패/지연/중복 발송 대응 런북을 운영한다.
- 사용자 수신 거부/민원 접수 시 처리 SLA와 재발 방지 절차를 적용한다.
- 알림 품질 지표(발송 성공률, 중복률, 거부율)를 주기적으로 모니터링한다.
- 장애 복구 재발송은 사용자별 빈도 상한 정책을 위반하지 않도록 제한한다.
- 관심업종 푸시 볼륨 급증 시 즉시 적용 가능한
카테고리 푸시 kill-switch를 운영한다. - 관심매장/관심업종별 전환율(열람-예약 전환)과 수신 거부율을 분리 추적한다.
- 관심업종은 보류 상태 동안
알림 비활성이 유지되는지 점검 체크를 운영한다. - 관심업종 재개 판단은 월 단위 운영 리뷰에서만 수행한다.
- 관심매장 카운트 재집계 배치(1시간)의 성공률/지연/누락을 모니터링한다.
8. Implementation Contract (Optional)
8.1 API Contract
- 관심상점/관심업종 등록은 업서트(upsert)로 처리한다.
- 해제 요청은 멱등 삭제로 처리한다.
- 슬롯 목록 응답은
is_favorite_store를 포함한다. - 슬롯 목록 정렬 파라미터는 기본값을 기존 정렬로 유지하고, 선택값
favorite_first를 허용한다. - 비로그인 슬롯 목록 응답은
is_favorite_store=false로 고정하고 토글 액션은 인증 필요 오류를 반환한다. - 관심업종 기능 플래그가
off이면 관심업종 등록/해제 API는 FEATURE_DISABLED를 반환한다.
8.2 Data Contract
user_favorite_store(user_id, store_id) 유니크 제약을 강제한다.user_interest_category(user_id, category_id) 유니크 제약을 강제한다.user_interest_category.category_id는 ADR-005 canonical 업종(DINING|CAFE_DESSERT|BAR|BEAUTY|FITNESS|ACTIVITY|WELLNESS|ETC)만 허용한다.user_push_tokens.expo_push_token은 전역 유니크여야 하며, 동일 토큰은 동시에 하나의 사용자에게만 활성 연결된다.user_notification_queue는 최소 user_id, slot_id, channel, status, payload, dedupe_key를 포함해야 한다.- 알림 레이트 리밋 키는 최소
user_id + subscription_type + time_bucket 단위로 저장한다. partners.favorite_user_count는 캐시 컬럼이며 SoT는 user_favorite_store로 고정한다.
8.3 Error/Observability Contract
- 비활성/삭제 대상 구독 요청은
SUBSCRIPTION_TARGET_NOT_AVAILABLE로 반환한다. - 구독 변경 이벤트와 알림 발송 이벤트를 분리 기록한다.
- 관심업종 푸시 상한 초과는
PUSH_RATE_LIMITED 이벤트로 기록한다. - 슬롯 카드 별표 토글 실패는
favorite_toggle_failed 이벤트로 기록한다.
8.4 Test/Acceptance Contract
- 동일 대상 중복 등록 시 레코드가 중복 생성되지 않아야 한다.
- 구독 해제 후 알림 후보에서 즉시 제외되어야 한다.
- 슬롯 카드에서 관심상점에만 채워진 별 마크가 노출되고 비관심 슬롯에는 별 마크가 노출되지 않아야 한다.
- 업종 필터가
카페일 때 관심업종 마트 슬롯은 강제 노출되지 않아야 한다. - 기본 정렬에서는 기존 슬롯 정렬 기준이 유지되어야 하고,
favorite_first 선택 시에만 관심매장 우선 정렬이 적용되어야 한다. - 관심매장 등록 기본 흐름은
슬롯 탭 -> 상세 화면에서 완료되어야 한다. - 1단계 릴리스에서 스와이프 액션 기반 관심매장 등록은 제공되지 않아야 한다.
- 롱프레스 보조 경로를 제공하는 경우 팝업에
관심매장 등록/해제 액션이 노출되어야 한다. - 파트너 매장정보에서 관심등록 수가 표시되어야 하며 최대 1시간 지연을 허용해야 한다.
- 관심매장 알림은 즉시형을 허용해야 한다.
- 사용자가 관심매장 알림을 꺼도 관심매장 저장 상태 자체는 유지되어야 한다.
- 탐색 메인 슬롯 카드에는 기본적으로 별표만 노출되고, 알림 아이콘은 관심매장 맥락 화면에서만 노출되어야 한다.
- 관심업종 기능 재개 시 알림은 사용자별 일 상한을 초과해 발송되지 않아야 한다.
- 관심업종 기능 플래그가
off인 동안 관심업종 UI가 노출되지 않아야 한다. - 관심업종 기능 플래그가
off인 동안 관심업종 알림 후보가 생성되지 않아야 한다.
8.5 Scenario Contract
- 시나리오 A (리스트 탐색): 사용자가 슬롯 리스트를 볼 때 관심매장에만 헤더 우측 별 마크가 보여야 하며 리스트 밀도가 과도하게 증가하지 않아야 한다.
- 시나리오 B (정렬 선택): 기본 진입에서는 기존 정렬이 유지되고, 사용자가
관심매장 우선 정렬을 선택한 경우에만 관심매장이 앞쪽에 배치되어야 한다. - 시나리오 C (등록 동선): 사용자가 슬롯을 탭해 상세로 진입한 뒤 관심매장 등록/해제를 수행할 수 있어야 한다.
- 시나리오 C-1 (보조 경로): 롱프레스가 활성인 빌드에서는 간단 팝업에서 관심매장 등록/해제 액션을 수행할 수 있어야 한다.
- 시나리오 C-2 (파트너 지표): 파트너가 매장정보 화면에서 관심등록 수를 확인할 수 있어야 하며 값은 시간 단위로 갱신되어야 한다.
- 시나리오 C-3 (알림 분리): 사용자가 관심매장을 유지한 채 알림만 끌 수 있어야 하며, 저장/재방문 기능은 계속 동작해야 한다.
- 시나리오 D (필터 충돌): 사용자가 업종 필터를 적용하면 관심업종 신호는 랭킹에만 영향 주고 필터 결과 자체를 바꾸지 않아야 한다.
- 시나리오 E (보류 상태): 관심업종 기능이 비활성인 릴리스에서는 관심업종 UI/알림이 동작하지 않아야 한다.
- 시나리오 F (재개 이후): 관심업종 기능 재개 시 일 상한/쿨다운/중복제거 정책으로 푸시 폭탄이 발생하지 않아야 한다.
- 시나리오 G (실시간성): 슬롯 오픈 이벤트가 발생하면 서버 이벤트 파이프라인을 통해 관심구독 알림 후보가 생성되어야 하며 클라이언트 폴링은 필수가 아니어야 한다.
9. Validation