Gemini API 마이그레이션: Frontend에서 Backend로의 여정
Gemini API 마이그레이션: 왜 그리고 어떻게 옮겼는가?
초기 프로토타입 단계에서는 빠른 개발을 위해 React Native 앱(Frontend)에서 직접 Google Gemini API를 호출했습니다. 하지만 서비스가 고도화되면서 보안 문제와 데이터 처리의 한계에 부딪혔고, 결국 FastAPI 백엔드로 로직을 완전히 이관(Migration)하게 되었습니다. 이 글에서는 그 과정에서의 고민과 해결책을 기록합니다.
1. 도입 배경: 왜 옮겨야 했나?
🚨 보안 취약점 (Security Vulnerability)
가장 치명적인 문제는 API Key 노출이었습니다.
클라이언트 사이드에 API Key를 심어두면, 앱을 디컴파일하거나 네트워크 패킷을 감청하는 것만으로도 키를 탈취할 수 있습니다. .env로 관리한다고 해도, 빌드된 앱 패키지 내부에는 결국 텍스트 형태로 키가 존재하게 됩니다. 이는 악의적인 사용자가 내 할당량(Quota)을 도용하거나 과금 폭탄을 유발할 수 있는 심각한 리스크였습니다.
🧩 비즈니스 로직의 파편화
단순히 AI 응답을 받는 것을 넘어, "분석된 식단 정보를 DB에 저장"하고 "사용자에게 리워드를 지급"하는 후속 로직이 필요했습니다. 클라이언트에서 이 모든 것을 처리하려면:
- Gemini API 호출
- 응답 파싱
- 백엔드 API 호출 (DB 저장)
- 백엔드 API 호출 (리워드 지급)
이 과정에서 네트워크가 끊기거나 앱이 종료되면 데이터 정합성이 깨지는 문제가 발생합니다. 트랜잭션 관리가 불가능한 구조였습니다.
2. 마이그레이션 과정의 난관들 (Pain Points)
📸 이미지 데이터 전송의 복잡성
Frontend에서 Gemini로 직접 보낼 때는 로컬 파일 경로만 있으면 SDK가 알아서 처리해줬습니다. 하지만 Backend로 보내려면 이미지 바이너리 데이터를 HTTP 요청에 실어 보내야 했습니다.
- 문제:
multipart/form-data로 이미지를 전송할 때, React Native의FormData객체 구성 방식과 FastAPI의UploadFile처리 방식 간의 미묘한 차이로 인해422 Unprocessable Entity에러가 빈번했습니다. - 해결: React Native에서는
uri,type,name필드를 명확히 지정한 객체를FormData에 append하고, FastAPI에서는UploadFile타입을 사용하여 스트림 형태로 이미지를 받도록 표준화했습니다.
⏳ 타임아웃과 응답 속도
Gemini의 멀티모달(이미지+텍스트) 분석은 시간이 꽤 걸리는 작업(수 초~십수 초)입니다.
- 문제: 모바일 네트워크 환경은 불안정하여 긴 요청이 자주 끊겼습니다. 또한 백엔드 서버(Gunicorn/Uvicorn)의 기본 타임아웃 설정에 걸려 연결이 강제로 종료되기도 했습니다.
- 해결:
- 백엔드 타임아웃 설정(
--timeout)을 넉넉하게 조정했습니다. - 클라이언트 UI에 명확한 로딩 인디케이터와 재시도(Retry) 로직을 구현하여 사용자 경험(UX)을 개선했습니다.
- 백엔드 타임아웃 설정(
3. 더 나은 방법을 찾아서: 개선된 아키텍처
단순히 위치만 옮기는 것을 넘어, 아키텍처를 개선할 기회로 삼았습니다.
📦 Pydantic을 활용한 구조화된 출력 (Structured Output)
Gemini는 기본적으로 텍스트를 반환합니다. 이를 JSON으로 파싱하는 것은 불안정했습니다.
FastAPI로 옮기면서 google-generativeai 라이브러리의 최신 기능을 활용, Pydantic 모델을 프롬프트에 직접 주입하거나 응답 스키마를 강제하여 항상 유효한 JSON을 받도록 개선했습니다.
# Backend (FastAPI)
class DietAnalysis(BaseModel):
is_healthy: bool
ingredients: List[str]
calories: int
# Gemini가 이 스키마에 맞춰서 JSON을 뱉어줌
response = model.generate_content(..., generation_config={"response_mime_type": "application/json"})🔄 비동기 처리와 트랜잭션 보장
이제 클라이언트는 "이미지 분석 요청" 한 번만 보내면 됩니다. 백엔드에서는:
- Gemini 분석 요청 (await)
- DB 트랜잭션 시작
- 분석 결과 저장
- 리워드 지급 로직 실행
- 트랜잭션 커밋
이 모든 과정이 원자적(Atomic)으로 처리되어 데이터 무결성이 보장되었습니다.
4. 결론 및 제언
Frontend에서 외부 API를 직접 호출하는 것은 초기 개발 속도에는 유리할지 몰라도, 보안과 확장성 측면에서 기술 부채가 됩니다.
특히 LLM/AI 모델을 사용하는 서비스라면, 백엔드를 Proxy이자 Orchestrator로 활용하는 것이 필수적입니다.
- API Key 숨기기: 서버 간 통신으로 키 노출 원천 차단
- 응답 정제: AI의 불확실한 출력을 서버에서 검증하고 클라이언트에는 깔끔한 데이터만 전달
- 로직 캡슐화: 클라이언트 업데이트 없이도 프롬프트나 모델을 변경 가능
이번 마이그레이션은 단순한 코드 이동이 아니라, 서비스의 안정성을 위한 필수적인 진화 과정이었습니다.