Skip to content

Backend

trz-21 edited this page Jun 13, 2026 · 1 revision

📌 이 페이지는 이 프로젝트의 살아있는 스펙(최종 현황) 이며 canonical 문서입니다. 코드/설계 변경 시 PR 머지 후 이 페이지를 갱신합니다. (작업 흐름: 개발-워크플로우)

백엔드 구조

위치: backend/ (monorepo). Cargo 패키지명 coupang-review-ai-backend, lib 이름 coupang_review_ai_backend.

아키텍처: Hexagonal Architecture (Ports & Adapters)

아래는 v1 목표 구조다. 초기 스캐폴드는 health 엔드포인트 + 빈 모듈로 시작하며, 기능 구현 시 .claude/code-rules.md의 "새 API 엔드포인트 추가 순서"를 따라 채운다.

backend/src/
├── lib.rs
├── config.rs
├── error.rs
│
├── domain/                    # Zero external deps
│   ├── mod.rs
│   ├── models/                # 핵심 엔티티
│   │   ├── mod.rs
│   │   ├── user.rs
│   │   └── analysis.rs
│   └── ports/                 # trait (port) 정의
│       ├── mod.rs
│       ├── user_repository.rs
│       ├── analysis_repository.rs
│       ├── crawler.rs
│       └── ai_analyzer.rs
│
├── application/               # 비즈니스 로직
│   ├── mod.rs
│   ├── auth_service.rs
│   └── analysis_service.rs
│
├── adapters/                  # 외부 의존성 구현체
│   ├── mod.rs
│   ├── postgres/
│   │   ├── mod.rs
│   │   ├── user_repo.rs
│   │   └── analysis_repo.rs
│   ├── coupang/               # 쿠팡 리뷰 크롤러
│   │   ├── mod.rs
│   │   └── crawler.rs
│   └── gemini/                # Gemini AI 분석
│       ├── mod.rs
│       └── analyzer.rs
│
└── http/                      # Axum 진입점
    ├── mod.rs
    ├── macros.rs
    ├── state.rs
    ├── router.rs
    └── handlers/
        ├── mod.rs
        ├── health.rs
        ├── auth.rs
        └── analysis.rs

레이어 역할

레이어 역할 외부 의존성
domain/ 엔티티 + Port trait 정의 없음
application/ 비즈니스 로직, Port 사용 domain만
adapters/ Port 구현체 (DB, 크롤러, AI) sqlx, reqwest, Gemini API
http/ Axum 핸들러, AppState, 라우터 axum, application

AppState 패턴

#[derive(Clone)]
pub struct AppState {
    pub db_pool: PgPool,
    pub config: Config,
    pub user_service: Arc<dyn UserService>,
    pub analysis_service: Arc<dyn AnalysisService>,
}
  • 앱 시작 시 한 번 초기화
  • Arc<dyn Trait>로 서비스 보관 → 테스트 시 Mock 주입 가능
  • Axum이 매 요청마다 Clone

에러 처리 패턴

#[derive(Debug, Error)]
pub enum AppError {
    NotFound,
    Unauthorized,
    Forbidden(String),     // 플랜 초과 등
    BadRequest(String),
    Database(#[from] sqlx::Error),
    Internal(#[from] anyhow::Error),
}

impl IntoResponse for AppError { ... }
pub type AppResult<T> = Result<T, AppError>;

DB 스키마

-- 사용자
CREATE TABLE users (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email       TEXT NOT NULL UNIQUE,
    password_hash TEXT NOT NULL,
    plan        TEXT NOT NULL DEFAULT 'free',  -- free | starter | pro
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- 분석 작업
CREATE TABLE analyses (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id     UUID NOT NULL REFERENCES users(id),
    urls        TEXT[] NOT NULL,
    review_limit INT NOT NULL DEFAULT 100,
    status      TEXT NOT NULL DEFAULT 'pending',  -- pending|crawling|analyzing|completed|failed
    result      JSONB,
    error       TEXT,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    completed_at TIMESTAMPTZ
);

분석 흐름 (비동기)

POST /analyses
    └─ analysis_service.create_analysis()
         └─ DB에 status=pending으로 저장
         └─ tokio::spawn() → 백그라운드 태스크
              ├─ status=crawling
              ├─ crawler.fetch_reviews(urls) → Vec<Review>
              ├─ status=analyzing
              ├─ ai_analyzer.analyze(reviews) → AnalysisResult
              ├─ status=completed, result 저장
              └─ (실패 시) status=failed, error 저장

GET /analyses/:id (프론트엔드 폴링)
    └─ status 확인 → completed이면 result 반환

크롤러 전략

  • 쿠팡 리뷰 JSON API: https://www.coupang.com/next-api/review?productId=...&page=1&size=N&sortBy=ORDER_SCORE_ASC&ratingSummary=true
  • 실제 브라우저 헤더(User-Agent, Referer, Accept, Accept-Language, sec-fetch-*) 설정
  • 응답 status 검사 → 실패 시 상태코드+본문 스니펫을 에러로 전파(진단 가능)
  • 파싱: 알려진 키 + 재귀 탐색으로 리뷰 배열/상품명 발견(스키마 변경 방어)
  • Port trait CoupangCrawler로 격리 → 테스트/데모 시 Mock 데이터 사용

anti-bot 한계와 운영 방식

  • 쿠팡은 Akamai Bot Manager 로 보호됨. 데이터센터 IP 는 IP 단에서 403(Access Denied) → 헤더/쿠키 트릭으로 우회 불가(검증됨).
  • sensor 스푸핑/프록시 로테이션 자체 구현은 하지 않음(취약 + ToS/법적 리스크).
  • 안정적 수집 경로(택1):
    • COUPANG_PROXY_URL: 스크래핑 API(ScraperAPI/ZenRows 등) 경유. {url} 치환, anti-bot/IP 로테이션을 벤더가 처리.
    • 가정용 IP/헤드리스 환경에서 실행.
  • 로컬 전체 플로우 데모: CRAWLER_MODE=mock(픽스처 리뷰) + 실제 Gemini 분석.
  • 실패 원인은 DB analyses.error(AppError::detail())와 서버 로그(tracing::error!)로 노출.

Gemini API 분석 프롬프트 전략

  • 엔드포인트: POST generativelanguage.googleapis.com/v1beta/models/{model}:generateContent (인증: x-goog-api-key 헤더)
  • 리뷰 배치 처리 (100개씩 분할)
  • 한국어 프롬프트
  • 구조화된 JSON 출력 요청 (generationConfig.responseMimeType = application/json)
  • 모델: gemini-2.5-flash (free tier 호출 가능, env GEMINI_MODEL 로 override, GEMINI_API_KEY 미설정 시 MockAiAnalyzer 폴백)

테스트 전략

  • testcontainers: cargo test 실행 시 PostgreSQL 컨테이너 자동 관리
  • TestApp: 격리된 test_<uuid> DB + 랜덤 포트 서버
  • 크롤러/Gemini API: Mock Adapter 주입
  • 상세: specification/dev/harness.md

📚 스펙 네비게이션: Home · Service-Overview · Tech-Stack · API · Backend · Frontend · Harness · 개발-워크플로우

Clone this wiki locally