본문으로 건너뛰기

의사결정 로그

✍️ 주요 의사결정 로그 (Architecture Decision Record) 최종 업데이트: 2025년 6월 29일

이 문서는 SurfAI 프로젝트를 진행하며 내렸던 주요 기술적 결정의 배경, 고려했던 대안, 그리고 최종 선택의 이유를 기록합니다.


1. 인증 시스템: JWT + HttpOnly 쿠키 방식 채택

  • 날짜: 2025-06-21
  • 논의:
    • 초기에는 전통적인 세션-쿠키 방식을 고려했습니다.
    • 이후, 확장성과 클라이언트(모바일 등) 호환성을 위해 JWT 토큰을 LocalStorage에 저장하는 방식을 논의했습니다.
    • 최종적으로, 보안과 편의성을 모두 고려하여 JWTHttpOnly 쿠키에 담아 전달하는 방식으로 결정했습니다.
  • 결정: JWT (Access/Refresh Token)를 생성하고, 이를 HttpOnly Secure 쿠키를 통해 클라이언트와 통신한다.
  • 이유:
    • 보안 강화 (XSS 방어): HttpOnly 속성을 통해, 브라우저의 JavaScript 코드가 토큰에 직접 접근하는 것을 막아 XSS(Cross-Site Scripting) 공격으로 인한 토큰 탈취 위험을 크게 줄일 수 있습니다.
    • 서버 Stateless 유지: JWT를 사용함으로써 서버는 사용자 세션 상태를 저장할 필요가 없어, 향후 서버를 수평적으로 확장(Scale-out)하기에 매우 유리한 구조를 가집니다.
    • 자동 인증: 프론트엔드는 apiClient에서 credentials: 'include' 옵션만 설정하면, 브라우저가 모든 요청에 자동으로 인증 쿠키를 포함시켜주므로 코드 관리가 단순해집니다.

2. 배포 플랫폼: Docker + Google Cloud Run 채택

  • 날짜: 2025-06-22
  • 논의:
    • 프론트엔드 배포를 위해 간편한 Cloudflare Pages/Vercel 사용을 고려했습니다.
    • 백엔드 배포를 위해 GCP App Engine(PaaS)과 Compute Engine(IaaS)을 검토했습니다.
    • 최종적으로 프론트엔드와 백엔드 모두 Docker 컨테이너화하여 Google Cloud Run에 배포하는 것으로 결정했습니다.
  • 결정: 프론트엔드(Next.js)와 백엔드(NestJS)를 모두 각각의 Docker 컨테이너로 빌드하고, Google Cloud Run에 배포한다.
  • 이유:
    • 환경의 일관성: Docker를 사용하여 로컬 개발 환경과 클라우드 운영 환경을 100% 동일하게 유지할 수 있습니다. "내 컴퓨터에선 됐는데..."와 같은 문제를 원천적으로 방지합니다.
    • 배포 유연성: Docker 컨테이너는 "웹 배포의 표준 규격"과 같습니다. 나중에 AWS, Azure 등 다른 클라우드 플랫폼이나 자체 서버로 이전해야 할 경우, 최소한의 변경으로 마이그레이션이 가능하여 특정 플랫폼에 종속되지 않습니다.
    • 통합된 CI/CD: 두 프로젝트 모두 "Docker 이미지 빌드 -> Cloud Run 배포"라는 동일한 형태의 CI/CD 파이프라인을 GitHub Actions에서 구성할 수 있어 관리가 용이합니다.

3. 도메인 아키텍처: 서브도메인 분리 방식 채택

  • 날짜: 2025-06-20
  • 논의:
    • 경로 기반 라우팅 (surfai.org/ vs surfai.org/api/): 단일 도메인을 사용하여 CORS 및 쿠키 문제를 근본적으로 해결할 수 있는 장점이 있었습니다. 하지만 앞단에 트래픽을 분배할 로드밸런서 설정이 추가로 필요했습니다.
    • 서브도메인 분리 (app.surfai.org vs api.surfai.org): 각 서비스의 역할을 명확히 분리하고, 독립적으로 관리하기에 용이한 표준적인 방식입니다.
  • 결정: 프론트엔드는 surfai.org, 백엔드는 api.surfai.org와 같이 서브도메인을 분리하여 운영한다.
  • 이유:
    • 초기 설정이 로드밸런서 구성보다 직관적이고 간단합니다. (Cloud Run의 커스텀 도메인 매핑 기능 활용)
    • 프론트엔드와 백엔드의 역할이 도메인 수준에서 명확하게 분리되어, 각 서비스에 대한 독립적인 보안 정책(방화벽, 캐싱 등)을 적용하기 용이합니다.
    • 복잡했던 크로스 도메인 쿠키 문제는, 백엔드에서 sameSite: 'none', domain: 'surfai.org'와 같이 올바른 쿠키 옵션을 설정함으로써 해결했습니다.

4. 데이터 보존 정책: 파일 자동 삭제, 메타데이터 영구 보존

  • 날짜: 2025-06-23
  • 논의:
    • 스토리지 비용 절감을 위해 2일이 지난 모든 데이터(파일+DB 레코드)를 삭제하는 방안을 고려했습니다.
    • 하지만 이 경우 사용자가 자신의 과거 작업 기록(프롬프트, 파라미터 등)을 영원히 잃게 되는 부정적인 사용자 경험이 우려되었습니다.
  • 결정: 실제 이미지/비디오 파일은 2일 후 Cloudflare R2에서 자동으로 삭제하되, 생성 기록 메타데이터(DB 레코드)는 사용자가 직접 삭제하기 전까지 영구적으로 보존한다.
  • 이유:
    • 가장 큰 용량을 차지하는 파일만 삭제하여 스토리지 비용을 효과적으로 절감할 수 있습니다.
    • 사용자는 자신의 "히스토리" 페이지에서 과거 작업의 상세 정보(어떤 프롬프트를 썼는지 등)를 계속 확인할 수 있어 사용자 경험을 보존할 수 있습니다.
    • 프론트엔드에서는 만료된 파일에 대해 "기간 만료" UI를 표시하여 사용자에게 명확한 피드백을 제공합니다.

5. 문서화 시스템: Docusaurus 채택

  • 날짜: 2025-07-07
  • 논의:
    • 초기에는 GitHub Wiki나 단순한 README.md 파일을 사용하여 문서를 관리하는 방안을 고려했습니다.
    • 더 체계적인 관리를 위해 GitBook과 같은 외부 SaaS 솔루션 사용도 검토했습니다.
    • 최종적으로, 코드와 문서를 동일한 저장소에서 관리하고, React 기반의 커스터마이징이 용이한 Docusaurus를 채택하기로 결정했습니다.
  • 결정: 프로젝트의 공식 문서화 도구로 Docusaurus를 사용하고, surfai-docs라는 별도의 모듈로 관리한다.
  • 이유:
    • 살아있는 문서 (Living Documentation): 모든 문서가 Markdown(.md, .mdx) 파일로 관리되므로, 코드와 함께 Git으로 버전 관리가 가능합니다. 이는 코드 변경과 문서 업데이트의 동기화를 용이하게 하여 항상 최신 상태를 유지하는 데 도움을 줍니다.
    • 개발자 친화성: 개발자들은 익숙한 Markdown 문법으로 문서를 작성하고, 필요시 React 컴포넌트를 문서 내에 직접 삽입(MDX)하여 인터랙티브한 문서를 만들 수 있습니다.
    • 쉬운 접근성: 빌드된 결과물은 정적 웹사이트이므로, 비개발 직군을 포함한 모든 팀원이 별도의 도구 없이 웹 브라우저만으로 항상 최신화된 문서에 쉽게 접근하고 검색할 수 있습니다.
    • 강력한 확장성: 다국어 지원, 문서 버전 관리, 검색 기능(Algolia) 등 복잡한 요구사항을 플러그인과 커스터마이징을 통해 손쉽게 해결할 수 있습니다.