B2B 파트너 연동 PRD

Background

sports-application은 현재 B2C 사용자(모바일 앱)와 내부 운영자(web /portal, /admin)만을 액터로 갖는다. goods.Productticketing.EventownerId(실제 컬럼은 owner_id) 필드로 소유자 개념을 갖고 있으나, 이 값은 OwnershipGuardImplUserPrincipal.id로 해석하는 User ID 네임스페이스를 그대로 사용한다 — 즉 현재 “소유자”는 로그인한 User 그 자체이며, User와 구분되는 “협력사”라는 별도 신원 타입이 존재하지 않는다. 등록 API(GoodsSellerApiController, EventHostApiController)는 각각 @PreAuthorize("hasRole('GOODS_SELLER')"), @PreAuthorize("hasRole('EVENT_HOST')")로 보호되어 있어, 이 role을 가진 User만 등록할 수 있다.

요구사항은 “B2B 사이드 외부 협력사가 상품 등록·관람 티켓 등록 유즈케이스를 하루 1,000건 이상 지속 발생시킨다”는 부하 시나리오를 요구하며, 이를 검증하려면 먼저 B2B 협력사가 실제로 상품·티켓을 등록할 수 있는 API·인증·권한 체계가 있어야 한다. 이 과제는 신규 Partner 도메인을 개발하되, 상품·티켓 등록의 비즈니스 로직 자체는 기존 goods·ticketing 도메인 서비스(정확히는 기존 CreateMyProductUseCase·CreateMyEventUseCase)를 그대로 경유해 호출한다 — Partner 도메인이 등록 로직을 중복 구현하지 않는다.

이 과제는 [도메인 경계 재설계](../도메인 경계 재설계/PRD.md)에서 확정하는 컨텍스트 맵(코어 도메인이 지원 도메인에게 유즈케이스를 제공하는 방향)을 전제로 한다.

Problem Definition

  • 외부 협력사가 상품·티켓을 등록하려면 현재 내부 운영자 전용 웹 포털(web/app/portal)을 통해서만 가능하다. 협력사 전용 API·인증 수단이 없다.
  • owner_id 네임스페이스 충돌: Product·Eventowner_id는 User ID를 가리킨다. Partner를 이 네임스페이스에 어떻게 편입할지(User로 가장하는 계정을 둘지, owner-type 구분 컬럼을 추가할지)가 결정돼 있지 않으면 기존 소유권 검증 로직(OwnershipGuardImpl, requireOwnedBy)이 깨진다.
  • role 게이트 충돌: 등록 API는 hasRole('GOODS_SELLER'/'EVENT_HOST')로 보호돼 있다. Partner 요청이 이 role 검사를 통과할 경로가 없으면 API Key 인증만으로는 등록이 불가능하다.
  • 유즈케이스 시그니처 비대칭: CreateMyProductUseCase는 소유자를 OwnershipGuard.authUserId()로 SecurityContext에서 직접 해석하는 반면, CreateMyEventUseCaseCreateMyEventCommand.ownerUserId를 Command 인자로 받는다. “기존 유즈케이스를 그대로 호출한다”는 것이 구체적으로 무엇을 의미하는지(코드 변경 없이 인증 계층에서만 해결할지, Command 생성 지점을 손댈지)가 정의돼 있지 않다.
  • 협력사의 등록 활동을 감사할 로그 체계가 없다 — 단 mcp 도메인에는 이미 McpAuditLog(엔티티) + McpAuditLogAsyncRecorder(비동기 기록기) + McpAuditLogDomainService 형태의 감사 로그 인프라 패턴이 존재한다. Partner 감사 로그가 이 패턴을 참조할지, mcp 테이블을 공유할지 결정이 필요하다.
  • 이 상태로는 요구사항의 “하루 1,000건 이상 지속 발생” B2B 트래픽을 만들 대상 API 자체가 없다.

Goals / Non-Goals

Goals

  • Partner(외부 협력사) 엔티티와 API Key 기반 인증을 도입한다.
  • owner_id 네임스페이스 결정: Partner를 User의 특수 유형으로 편입한다 — Partner 가입 시 로그인 불가능한 “연동 전용 User 계정”을 함께 생성하고, 이 User 계정에 기존 ROLE_GOODS_SELLER/ROLE_EVENT_HOST를 부여한다. 이렇게 하면 별도 owner-type 구분 컬럼을 추가하지 않고 기존 owner_id(User ID) 네임스페이스·기존 role 게이트·기존 OwnershipGuardImpl을 그대로 재사용할 수 있다.
  • Partner API Key 인증 필터가 요청을 인증하면, 위에서 만든 연동 전용 User 계정을 SecurityContext의 Authentication으로 채운다. 이후 CreateMyProductUseCase(SecurityContext에서 소유자 해석)·CreateMyEventUseCase(Controller가 SecurityContext에서 얻은 id를 Command에 채워 전달)는 코드 변경 없이 그대로 호출된다 — “경유”의 실제 접점은 인증 계층이다.
  • Partner API Key의 발급·재발급·폐기 라이프사이클을 제공한다.
  • Partner별 소유 리소스 접근 권한을 검증한다(타 Partner 리소스 접근 시 거부) — 기존 OwnershipGuardImpl을 그대로 재사용한다.
  • Partner의 등록·수정 활동에 대한 감사 로그를 남긴다 — mcpMcpAuditLog 패턴(비동기 기록기)을 참조하되, 도메인 분리 원칙에 따라 mcp 테이블을 공유하지 않고 별도 PartnerAuditLog로 구현한다.

Non-Goals

  • 정산·수수료 체계 — 본 과제는 등록 유즈케이스에 한정하며, 판매 대금 정산은 범위 밖이다. 향후 별도 과제로 검토한다.
  • Partner 전용 관리 UI(가입 신청·승인 화면, API Key 발급 화면, 감사 로그 조회 화면) — 본 과제는 API 전용 연동이며 UI는 제공하지 않는다. API Key 발급·재발급·폐기는 운영자가 기존 web/portal(또는 백엔드 관리 API)을 통해 수행하되, Partner 전용 신규 화면은 만들지 않는다. 필요해지면 별도 과제로 분리한다.
  • 실시간 협력사 매출 대시보드 — 별도 과제로 검토한다.

User Scenarios

페르소나: 외부 협력사 담당자(체육용품 벤더, 경기·공연 주최사).

  1. 해피 패스 — 상품 등록: 발급받은 API Key로 인증 헤더를 실어 상품 등록 API를 호출한다 → 인증 필터가 Partner의 연동 전용 User 계정으로 SecurityContext를 채운다 → CreateMyProductUseCase가 코드 변경 없이 그대로 호출되어 상품이 owner_id=Partner 연동 User 소속으로 생성된다.
  2. 해피 패스 — 티켓 등록: API Key로 인증해 이벤트 등록 API를 호출한다 → CreateMyEventUseCase가 코드 변경 없이 그대로 호출되어 이벤트·좌석이 생성된다.
  3. 예외 — 잘못된 요청: 무효한 카테고리 값을 담아 상품 등록을 요청하면 400을 받는다.
  4. 예외 — 권한 없음: Partner A가 Partner B 소유 상품을 수정하려 하면 404를 받는다(기존 OwnershipGuardImpl.requireOwned가 소유자 불일치 시 ResourceNotFoundException을 던져 리소스 존재 자체를 은닉하는 기존 동작을 그대로 재사용 — 코드 변경 없음).
  5. 예외 — 인증 만료: 폐기된 API Key로 요청하면 401을 받고, 재발급 절차 안내를 받는다.
  6. 예외 — API Key 재발급: 기존 키가 유출 의심될 때 재발급을 요청하면 이전 키는 즉시 무효화되고 신규 키가 발급된다.
  7. 빈 상태: 신규 가입한 Partner가 등록 이력 조회 API를 호출하면 빈 목록을 받는다.

Benchmarking

제품명카테고리참조 패턴URL
쿠팡 마켓플레이스 Open API이커머스 오픈마켓 셀러 API판매자가 API Key 발급 후 상품을 직접 등록하며, 카테고리·배송지 등 필수 정보를 API 계약으로 강제한다. 판매자(Partner)가 플랫폼 코어 상품 로직을 그대로 사용하는 구조를 참조Coupang Open API — 상품 API
카페24 Open API쇼핑몰 빌더 파트너 API외부 파트너사가 API Key/OAuth로 인증해 쇼핑몰 데이터(상품 등)에 접근하는 표준 파트너 연동 구조를 참조카페24 Open API 안내

Functional Requirements

ID요구사항우선순위
FR-1Partner 엔티티(id, name, apiKeyHash, status(ACTIVE/SUSPENDED), 생성일, 연동 User 계정 id)를 도입한다P0
FR-2Partner 가입 시 로그인 불가능한 연동 전용 User 계정을 함께 생성하고, 기존 ROLE_GOODS_SELLER·ROLE_EVENT_HOST를 부여한다P0
FR-3API Key 기반 Partner 인증 필터를 구현한다 — 인증 성공 시 연동 전용 User 계정으로 SecurityContext를 채우고, 유효하지 않거나 폐기된 키는 401을 반환한다P0
FR-4Partner의 상품 등록 요청은 기존 CreateMyProductUseCase를 코드 변경 없이 그대로 호출한다(인증 계층에서만 소유자가 해석됨)P0
FR-5Partner의 이벤트(관람 티켓) 등록 요청은 기존 CreateMyEventUseCase를 코드 변경 없이 그대로 호출한다(Controller가 SecurityContext에서 얻은 id를 Command에 채운다)P0
FR-6API Key 발급·재발급·폐기 라이프사이클을 제공한다 — 재발급 시 이전 키는 즉시 무효화된다P0
FR-7Partner는 자기 소유 리소스만 조회·수정할 수 있다(기존 OwnershipGuardImpl.requireOwned 재사용, 코드 변경 없음) — 타 Partner 리소스 접근 시 404를 반환한다(소유자 불일치를 ResourceNotFoundException으로 처리해 리소스 존재를 은닉하는 기존 동작)P1
FR-8Partner의 등록·수정 활동에 대한 감사 로그(PartnerAuditLog: 요청 시각, 요청자, 대상 리소스, 결과 상태)를 mcp의 비동기 기록기 패턴을 참조해 남긴다P1
FR-9SUSPENDED 상태의 Partner는 등록·수정 요청이 거부된다P2

Non-Functional Requirements

  • 상품·이벤트 등록 API의 P95 응답 시간은 300ms 이내다(동기 등록, DB 쓰기 1건 기준).
  • 하루 1,000건 이상의 등록 요청을 지속 처리해도 에러율 1% 미만을 유지한다([상시 트래픽 시뮬레이터](../상시 트래픽 시뮬레이터/PRD.md) B2B 시나리오로 검증).
  • API Key 폐기 후 1분 이내 신규 요청이 차단된다.

Operations

  • Partner별 등록 성공·실패율을 [옵저버빌리티 스택 도입](../옵저버빌리티 스택 도입/PRD.md) 대시보드에 노출한다.
  • Partner 등록 실패율이 급증하면 [지능형 장애 알림](../지능형 장애 알림/PRD.md) 채널로 통지한다(알림 소스 태그: partner).
  • 감사 로그 조회는 API 전용으로 제공하며(Non-Goals에 따라 전용 화면은 없음), 필요 시 운영자가 기존 web/portal에서 조회한다(포털 화면 확장 여부는 Open Questions).

Success Metrics

  • Partner 등록 API 에러율 1% 미만.
  • 모든 등록 요청의 감사 로그 커버리지 100% — 측정 방법: 일정 기간 동안의 등록 API 성공·실패 응답 건수(Access 로그 집계)와 PartnerAuditLog 적재 건수를 대조해 누락 0건을 확인한다.
  • 하루 1,000건 이상 지속 처리 검증 완료([상시 트래픽 시뮬레이터](../상시 트래픽 시뮬레이터/PRD.md) 결과로 확인).

Milestones

  • M1: Partner 엔티티·연동 User 계정·API Key 인증(FR-1~FR-3, 선행).
  • M2: 상품 등록 경유 연동(FR-4).
  • M3: 티켓 등록 경유 연동(FR-5).
  • M4: API Key 라이프사이클·권한 검증·감사 로그·SUSPENDED 거부(FR-6~FR-9).

의존: [도메인 경계 재설계](../도메인 경계 재설계/PRD.md)의 컨텍스트 맵(코어 도메인 → 지원 도메인 제공 인터페이스 확정)이 선행돼야 M2·M3의 “경유 호출” 설계가 확정된다. 본 과제 완료 후 [상시 트래픽 시뮬레이터](../상시 트래픽 시뮬레이터/PRD.md)의 B2B 시나리오가 이 API를 대상으로 동작한다.

Open Questions

  • 정산·수수료는 범위 밖으로 확정했으나, 향후 별도 과제로 예정할지 결정이 필요하다.
  • 운영자가 Partner 가입·API Key 발급을 수행할 최소한의 관리 경로(백엔드 관리 API만 제공할지, 기존 web/portal에 최소 화면을 추가할지)를 TDD 단계에서 결정한다 — Non-Goals에서 확정한 것은 “Partner 전용 신규 UI를 만들지 않는다”는 것이며, 운영자가 아예 수동 개입 없이 Partner를 등록할 수 없다는 뜻은 아니다.

Document History

날짜변경 내용
2026-07-03최초 작성
2026-07-03재검수 1차 반영: owner_id 네임스페이스 충돌 해소(연동 전용 User 계정 방식으로 결정), role 게이트 통과 경로 명시, 유즈케이스 시그니처 비대칭 해소(코드 불변, 인증 계층에서만 해결), API Key 라이프사이클 FR 추가(FR-6), Non-Goals의 “Partner 전용 UI”를 확정으로 고정하고 모순되던 Open Questions 항목 제거, 감사 로그 커버리지 측정 방법 명시, FR-9(SUSPENDED 거부)를 M4에 배치, AS-IS에 mcp의 McpAuditLog 인프라 존재 반영
2026-07-03재검수 2차 반영: FR-7·User Scenario 4의 상태코드를 403→404로 정정 — OwnershipGuardImpl.requireOwned가 소유자 불일치 시 ResourceNotFoundException(404)을 던지는 실제 동작에 맞춤(리소스 존재 은닉이라는 기존 보안 패턴 유지, 코드 변경 없음 전제는 그대로 유지)