02. 똑똑한 백엔드 창고 만들기 (FastAPI & DB)
"Serverless 환경에서도 우리는 강력한 Python 백엔드를 가질 수 있습니다."
단순히 데이터를 저장하는 것을 넘어, AI가 개입하고 수강신청의 복잡한 규칙을 계산할 백엔드 서버를 Firebase Functions 위에 구축해 봅시다.
1. FastAPI Integration: 서버리스와 파이썬의 만남
Firebase Functions v2는 기본적으로 개별 함수 단위로 동작하지만, 우리는 FastAPI를 통째로 얹어 일반적인 웹 서버처럼 다룰 것입니다.
🛠️ FastAPI를 Firebase Functions에 통합하기
1️⃣ 필수 패키지 설치
functions/requirements.txt에 다음 패키지들을 추가합니다:
2️⃣ 수동 ASGI 어댑터 구현
Firebase Functions v2는 ASGI(비동기 서버 인터페이스)를 직접 지원하지 않습니다. 하지만 수동 ASGI 어댑터를 구현하면 FastAPI의 모든 기능을 사용할 수 있습니다.
📌 참고: 이 구현 방식은 Reddit 커뮤니티 토론을 참조했습니다.
functions/main.py에 다음 코드를 작성합니다:
# functions/main.py
from firebase_functions import https_fn, options
import firebase_admin
from firebase_admin import initialize_app
from fastapi import FastAPI
import asyncio
import json
# Firebase Admin 초기화 (중복 방지)
if not firebase_admin._apps:
initialize_app()
# 전역 지역 설정 (서울)
options.set_global_options(region="asia-northeast3")
# FastAPI 앱 생성
app = FastAPI()
@app.get("/api/health")
def health_check():
return {"status": "ok"}
@https_fn.on_request()
def fastapi_handler(req: https_fn.Request) -> https_fn.Response:
"""
수동 ASGI 어댑터 구현
Firebase Functions v2에서 FastAPI를 실행하기 위해 ASGI 프로토콜을 직접 구현
"""
try:
# ASGI scope 구성
asgi_request = {
"type": "http",
"method": req.method,
"path": req.path,
"headers": [(k.lower().encode(), v.encode()) for k, v in req.headers.items()],
"query_string": req.query_string or b"",
}
# ASGI receive 함수
async def receive():
return {"type": "http.request", "body": req.get_data() or b"", "more_body": False}
# 응답 데이터 수집
response_body, response_headers, response_status = [], [], 200
# ASGI send 함수
async def send(message):
nonlocal response_body, response_headers, response_status
if message["type"] == "http.response.start":
response_status = message.get("status", 200)
response_headers = message.get("headers", [])
elif message["type"] == "http.response.body":
response_body.append(message.get("body", b""))
# FastAPI를 asyncio로 실행
async def run_asgi():
await app(asgi_request, receive, send)
asyncio.run(run_asgi())
# 응답 조합
full_body = b"".join(response_body)
headers_dict = {
k.decode() if isinstance(k, bytes) else k: v.decode() if isinstance(v, bytes) else v
for k, v in response_headers
}
return https_fn.Response(response=full_body, status=response_status, headers=headers_dict)
except Exception as e:
return https_fn.Response(
response=json.dumps({"error": f"Internal Server Error: {str(e)}"}),
status=500,
headers={"Content-Type": "application/json"},
)
주요 포인트:
- Firebase Functions v2는 ASGI를 직접 지원하지 않음
- ASGI 프로토콜(scope, receive, send)을 수동으로 구현
asyncio.run()으로 FastAPI의 비동기 핸들러를 실행- 모든 FastAPI 기능(라우팅, validation, async/await) 사용 가능
2. Firestore Design: NoSQL 데이터 모델링
전통적인 엑셀 방식(SQL)과 달리, Firestore는 JSON 문서(Document) 형태의 자유로운 구조를 가집니다.
🗄️ 수강신청 시스템 설계 (Collection)
이제부터는 AI와 함께 시스템을 설계하는 단계입니다.
단순히 코드만 짜달라고 하는 게 아니라, 기획서(prd.md)를 보고 데이터 구조를 먼저 설계하도록 시키세요.
- AI에게
prd.md읽히기: 프로젝트 루트의prd.md내용을 복사해서 AI에게 제공합니다. - 클래스 다이어그램 요청: 기획서를 바탕으로 필요한 데이터 모델들을 찾고, 그 관계를 그려달라고 합니다.
- Firestore 컬렉션 설계: 다이어그램을 바탕으로 NoSQL인 Firestore에 어떻게 저장할지 결정합니다.
AI 프롬프트 예시
prd.md 파일을 참고해서 수강신청 시스템에 필요한 데이터 모델을 설계해줘.
- 먼저 이 시스템에 필요한 클래스 다이어그램(Class Diagram)을 그려서 보여줘. (User, Course, Enrollment 등)
- 그리고 이걸 Firestore(NoSQL) 데이터베이스에 저장하려면 어떤 컬렉션(Collection)들이 필요할지 제안해줘.
💡 DB Seeding (초기 데이터 주입)
아무것도 없는 DB는 테스트하기 어렵습니다. AI에게 요청하여 seed.py 스크립트를 만들고, 강의 목록을 로컬 에뮬레이터에 한 방에 밀어 넣어 봅시다.
# 로컬 에뮬레이터에 데이터 주입
export FIRESTORE_EMULATOR_HOST="127.0.0.1:8080"
export GCLOUD_PROJECT="course-registration-79d64"
python seed.py
확인 방법: 에뮬레이터 UI(
http://localhost:4000)의 Firestore 탭을 클릭해보세요.courses컬렉션에 데이터가 가득 찬 것을 볼 수 있습니다!
AI 프롬프트 예시
seed.py 파일을 만들어서 firebase emulator에 강의, 학생, 수강 정보 샘플 데이터를 만들어 넣어야해.
docs/prd.md파일을 참고해서 기본 모델을 만들어주고, 그 모델을 바탕으로 firestore 컬렉션을 생성하는 구조로 만들어줘.- 만들어진 샘플데이터는 테스트 용도로 사용할거야.
3. CORS & Safety: 보안 울타리 치기
백엔드가 완성되어도 프론트엔드에서 접속하지 못한다면 무용지물입니다. CORS(Cross-Origin Resource Sharing) 설정을 통해 허락된 도메인만 접속을 허용합니다.
# functions/main.py 일부
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:5173", # 로컬 개발
"http://127.0.0.1:5002", # Firebase Hosting Emulator
"http://localhost:5002",
"https://<project-id>.web.app", # Firebase Hosting 도메인
"https://<project-id>.firebaseapp.com",
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
비용 폭탄 방지 설정 (Resource Limits)
서버리스는 호출된 만큼 돈을 냅니다. 무한 루프나 공격에 대비해 전역 옵션에 제한 설정을 반드시 추가해야 합니다.
4. [Check] 첫 번째 API 호출 테스트
모든 설정이 끝났다면, 브라우저나 Postman을 통해 우리 백엔드가 살아있는지 확인합니다.
- Endpoint:
http://127.0.0.1:5001/<project-id>/asia-northeast3/fastapi_handler/api/health - Expected Response:
{"status": "ok"}
5. 핵심 정리
| 용어 | 설명 | 비유 |
|---|---|---|
| FastAPI | 빠르고 현대적인 Python 웹 프레임워크 | 주방의 최신식 조리기구 |
| NoSQL (Firestore) | 정해진 틀 없이 유연하게 데이터를 쌓는 방식 | 포스트잇 게시판 |
| CORS | 다른 도메인에서의 리소스 요청을 관리하는 보안 정책 | 외부인 출입 검문소 |
| ASGI | 비동기 웹 서버와 애플리케이션 간의 표준 인터페이스 | 주문서 전달용 통신 시스템 |
준비 완료! 이제 똑똑한 두뇌(FastAPI)와 든든한 창고(Firestore)가 마련되었습니다. 다음 04장에서는 이 복잡한 시스템을 실제 배포하기 전, 내 컴퓨터 안에서 완벽하게 시뮬레이션하고 검증하는 에뮬레이터 및 TDD 전략을 배워보겠습니다!
실습 과제: FastAPI의 @app.get("/api/courses") 엔드포인트를 만들고, Firestore에서 전체 강의 목록을 가져와 JSON으로 반환하는 코드를 AI와 함께 완성해 보세요.