최종 프로젝트: 게임 기록·랭킹 REST API 완성 및 배포 준비
26강. 최종 프로젝트: 게임 기록·랭킹 REST API 완성 및 배포 준비
1. 이번 강의에서 해결할 문제
각 강의에서 만든 기능이 따로 동작해도 배포 가능한 API가 되지는 않습니다. 요청 흐름, DB 변경, 보안, 테스트, 실행 설정을 하나의 완료 기준으로 통합합니다.
2. 학습 목표
3. 핵심 개념
최종 요청 흐름은 Router → Pydantic → 인증·DB dependency → service/ORM → response model입니다. 완성 기준은 정상 응답뿐 아니라 잘못된 입력, 만료 token, 다른 사용자 소유권, 빈 검색, DB migration, 프로세스 재시작, 환경 변수 누락을 처리하는 것입니다.
랭킹은 게임별 사용자 최고 점수만 집계한다는 정책으로 통일합니다. 같은 사용자가 같은 게임을 여러 번 플레이해도 자신의 최고 점수 하나가 순위에 반영됩니다.
가장 작은 수직 조각부터 검증하기
전체 인증·CRUD·배포를 한 번에 연결하지 않습니다. 먼저 seed 기록 → GET /rankings/{game_name} → Pydantic 응답 → HTTP 200 → 테스트 한 줄을 닫습니다. 이후 기록 생성과 소유권, JWT, CORS를 같은 요청 경계에 붙입니다.
| 계층 | 받는 값 | 책임·결과 |
|---|---|---|
| Router | path·query·인증 사용자 | HTTP 계약과 상태 코드 |
| Pydantic schema | JSON 입력·ORM 결과 | 타입·범위 검증과 공개 응답 필드 |
| dependency | 요청 | DB session·현재 사용자 제공 |
| service·ORM | 검증된 값 | 거래·소유권·집계 정책 실행 |
| DB migration | revision | 코드가 기대하는 schema 재현 |
예를 들어 Alice가 100점과 150점, Bob이 120점을 기록했다면 랭킹은 Alice 150점 1위, Bob 120점 2위여야 합니다. 단순히 모든 기록을 정렬하면 Alice가 두 번 나타나므로 개인 최고 점수를 먼저 묶습니다.
4. 단계별 코드 예시
시작 전 상태와 구현 순서
Python 가상환경이 활성화되고 테스트용 DB URL과 운영용 secret이 코드 밖 환경 변수로 설정되어야 합니다. alembic current, python -m pip check, git status --short를 먼저 기록합니다. 실제 운영 DB가 아닌 로컬·테스트 DB에서 진행합니다.
- health와 DB 연결을 확인합니다.
- 사용자 두 명과 게임 기록 세 건으로 랭킹 수직 조각을 검증합니다.
- 기록 CRUD에 인증·소유권 실패를 추가합니다.
- migration으로 빈 DB를 구성합니다.
- 전체 pytest와 HTTP smoke test를 실행합니다.
- reload 없는 시작 명령과 health path를 문서화합니다.
파일 경로: game-ranking-api/
game-ranking-api/
├─ alembic/ # schema 변경 이력
├─ app/
│ ├─ models/ # User, GameRecord ORM
│ ├─ routers/ # auth, records, rankings, admin
│ ├─ schemas/ # 요청·응답 계약
│ ├─ services/ # 기록·랭킹 도메인 쿼리
│ ├─ config.py
│ ├─ database.py
│ ├─ dependencies.py
│ ├─ security.py
│ └─ main.py
├─ tests/
├─ .env.example
├─ alembic.ini
├─ requirements.txt
└─ README.md
파일 경로: game-ranking-api/app/routers/rankings.py
from fastapi import APIRouter
from sqlalchemy import func, select
from app.dependencies import DbSession
from app.models.record import GameRecord
from app.models.user import User
router = APIRouter(prefix="/rankings", tags=["rankings"])
@router.get("/{game_name}")
def game_ranking(game_name: str, db: DbSession, limit: int = 10) -> dict[str, object]:
personal_best = (
select(
GameRecord.user_id,
func.max(GameRecord.score).label("best_score"),
)
.where(GameRecord.game_name == game_name)
.group_by(GameRecord.user_id)
.subquery()
)
statement = (
select(User.username, personal_best.c.best_score)
.join(personal_best, personal_best.c.user_id == User.id)
.order_by(personal_best.c.best_score.desc(), User.username)
.limit(min(max(limit, 1), 100))
)
rows = db.execute(statement).all()
return {
"game_name": game_name,
"items": [
{"rank": index, "username": row.username, "score": row.best_score}
for index, row in enumerate(rows, start=1)
],
}
파일 경로: game-ranking-api/README.md
# Game Ranking API
## 실행
1. Python 3.13 가상환경 생성
2. `python -m pip install -r requirements.txt`
3. `.env.example`을 참고해 환경 변수 설정
4. `alembic upgrade head`
5. `python -m uvicorn app.main:app --host 0.0.0.0 --port 8000`
## 주요 API
- POST `/auth/register`, POST `/auth/token`
- GET/POST/PATCH/DELETE `/records`
- GET `/rankings/{game_name}`
- GET `/users/me`, GET `/health`
실행 위치: game-ranking-api/
python -m pip check
alembic upgrade head
alembic check
python -m pytest -q
python -m uvicorn app.main:app --host 0.0.0.0 --port 8000
서버를 실행한 터미널은 그대로 두고 새 PowerShell에서 실제 HTTP 응답을 확인합니다.
Invoke-RestMethod 'http://127.0.0.1:8000/health'
Invoke-RestMethod 'http://127.0.0.1:8000/rankings/Arena?limit=2' |
ConvertTo-Json -Depth 5
앞서 설명한 세 기록이 테스트 DB에 있다면 핵심 응답은 다음 형태입니다.
{
"game_name": "Arena",
"items": [
{"rank": 1, "username": "Alice", "score": 150},
{"rank": 2, "username": "Bob", "score": 120}
]
}
HTTP·테스트에서 확인할 구체 결과
| 요청·조건 | 기대 결과 |
|---|---|
| 정상 랭킹 | 200, 사용자당 한 항목, 점수 내림차순 |
limit=0 | 현재 코드 정책에 따라 1개로 제한됨 |
| 존재하지 않는 게임 | 200과 빈 items 또는 문서화한 정책 |
| token 없음으로 보호 API 호출 | 401 |
| 다른 사용자 기록 수정 | 403, DB 값 유지 |
| 잘못된 score 타입 | 422 |
| 중복 정책 위반 | 409와 일관된 오류 body |
| migration 없는 빈 DB | 시작 또는 쿼리 실패를 배포 전 발견 |
배포 환경에서는 reverse proxy 또는 플랫폼이 HTTPS를 종료하고, 프로세스 수와 DB connection pool 크기를 함께 계산합니다. 시작 명령과 health check path /health를 배포 설정에 등록합니다.
5. 코드가 동작하는 이유
subquery가 사용자별 최고 점수를 먼저 계산하고 User와 join해 이름을 가져옵니다. 정렬은 점수 내림차순, 동점은 사용자 이름으로 고정해 결과가 안정적입니다. 환경 변수와 migration이 코드·DB를 같은 버전으로 만들고 테스트가 핵심 계약을 배포 전에 확인합니다.
요청 데이터는 Router에서 타입 검증을 거쳐 DB session과 함께 쿼리로 이동합니다. subquery 결과에는 사용자당 한 행만 남고, 바깥 쿼리가 공개 가능한 username을 결합합니다. enumerate는 정렬이 끝난 결과에 1부터 순위를 붙이므로 DB 정렬 정책과 응답 rank가 일치합니다.
6. 자주 하는 실수와 해결법
7. 직접 실습
8. 이해 점검 질문 3개
9. 핵심 요약
최종 프로젝트: 게임 기록·랭킹 REST API 완성 및 배포 준비 미니 퀴즈
선택 즉시 정답과 해설을 확인할 수 있습니다. 결과는 이 브라우저에만 저장됩니다.
학습을 마쳤나요?
직접 실습과 점검 질문까지 확인한 뒤 완료로 표시하세요.