1. 프롤로그: 왜 FastAPI인가?
평소 Java/Spring Boot로 개발하던 나에게 Python의 FastAPI는 신선한 충격이었다. "빠르다", "직관적이다"라는 소문만 듣다가, 실제로 URL 단축 서비스(Shorten URL Service)를 만들어보며 Spring의 아키텍처를 Python에 녹여내는 과정을 기록해 본다.
특히 가장 고민했던 부분은 "자유분방한 파이썬 코드에 어떻게 견고한 구조(Architecture)를 입힐 것인가"였다.
2. 프로젝트 세팅: Modern Python Stack
Spring에 Gradle과 application.yml이 있다면, 이번 프로젝트에선 다음과 같은 스택을 사용했다.
- 패키지 관리: Poetry (Python의 Gradle 느낌. 의존성 관리가 아주 깔끔함)
- 환경 설정: Pydantic Settings (타입 안전하게 .env 관리)
- DB: PostgreSQL + SQLAlchemy (ORM) + Docker Compose
💡 Spring 개발자용 비교
| 구분 | Spring Boot | FastAPI Project |
| 빌드 도구 | Gradle / Maven | Poetry |
| 설정 파일 | application.yml | .env + Pydantic BaseSettings |
| 서버 엔진 | Tomcat (Embed) | Uvicorn (ASGI) |
3. 아키텍처 고민: Controller에 다 때려 넣지 말자
처음 튜토리얼을 보면 보통 main.py 파일 하나에 DB 연결, 로직, 라우팅을 다 넣는다.
하지만 실무 레벨 프로젝트라면 **계층형 아키텍처(Layered Architecture)**가 필수다.
나는 Spring의 Controller - Service - Repository 구조를 그대로 가져가기로 했다.

[Before] 라우터가 DB까지 관리하는 혼종 코드
처음엔 라우터(Router)에서 DB Session을 받아서 Service에 넘겨주는 방식을 썼다.
동작은 하지만 이게 과연 기존 계층형 아키텍처(Layered Architecture)에 맞는 코드일까? 관리나 테스트 코드에서 문제가 없을까? 라는 의문이 들었다.
@router.post("/")
# 라우터가 DB 세션을 왜 알아야 하지? (의존성 결합)
async def create_url(dto: URLCreate, db: AsyncSession = Depends(get_db)):
service = UrlService(db) # 컨트롤러에 db를 직접접근?... 뭔가 심적으로 불편하다.
return await service.create(dto)
[After] 완벽한 의존성 분리 (DI Chaining)
FastAPI의 강력한 Dependency Injection 시스템인 Depends를 활용해 체이닝을 걸었다.
이제 라우터는 DB를 몰라도 된다. 오직 Service만 바라본다.
# Router
@router.post("/")
async def create_url(dto: URLCreate, service: UrlService = Depends()):
return await service.create(dto)
# Service
class UrlService:
# Service는 Repository를 주입받음
def __init__(self, repo: UrlRepository = Depends()):
self.repo = repo
# Repository
class UrlRepository:
# Repository가 비로소 DB 세션을 주입받음
def __init__(self, db: AsyncSession = Depends(get_db)):
self.session = db
이 구조로 바꾸니 **테스트 코드 작성(Mocking)**이 쉬워지고, 역할 분담이 확실해졌다. 이것이 Pythonic한 Clean Architecture!
4. 핵심 개념: Gunicorn vs Uvicorn
배포 단계에서 가장 헷갈렸던 건 **"서버를 실행하는 명령어"**였다.
Spring은 그냥 jar 실행하면 끝인데, Python은 Gunicorn과 Uvicorn을 섞어 쓴다.
- Uvicorn (일꾼): 비동기 처리를 담당하는 빠른 엔진 (Netty 느낌). 하지만 프로세스 관리를 못함.
- Gunicorn (반장): 프로세스를 여러 개 띄우고 관리하는 매니저 (Tomcat 컨테이너 느낌).
Python은 GIL(Global Interpreter Lock) 때문에 한 프로세스가 CPU 하나밖에 못 쓴다. 그래서 Gunicorn으로 프로세스를 여러 개 띄우고(Multi-Process), 각 프로세스 안에서 Uvicorn이 비동기 처리(Async IO)를 하는 구조를 잡아야 성능을 뽑을 수 있다.
# Dockerfile 예시
CMD ["gunicorn", "-k", "uvicorn.workers.UvicornWorker", "app.main:app"]
"반장(Gunicorn)이 일꾼(UvicornWorker)들을 데리고 서버를 띄운다."
5. 삽질과 배움 (Troubleshooting)
이슈 1: AttributeError: 'Depends' object has no attribute ...
클래스 내부에서 repo = UrlRepository() 처럼 습관적으로 new를 했다가 에러가 났다.
- 원인: Depends는 FastAPI가 라우터를 실행할 때만 주입해주는 마법이다. 직접 호출할 땐 동작하지 않는다.
- 해결: 수동으로 호출할 땐 반드시 의존성(Session 등)을 직접 넣어줘야 한다.
이슈 2: Response Model 에러
라우터의 리턴 타입 힌트에 -> UrlService를 적었다가 Pydantic 에러가 났다.
- 원인: Service 객체는 JSON으로 변환할 수 없으니까.
- 해결: -> URLResponse 처럼 명확한 DTO(Schema)를 적어주거나, 타입 힌트를 생략해야 한다.
6. 마치며
Spring 개발자로서 Python 생태계는 낯설었지만, 원리는 통했다.
특히 Pydantic의 강력한 검증 기능과 FastAPI의 유연한 DI 시스템은 Spring 못지않게 매력적이었다.
결론: "언어는 달라도 좋은 아키텍처에 대한 고민은 같다."
다음으로는 FastApi에서 기용하는 Gunicorn, Uvicorn과 WGSI, ASGI를 알아볼 예정이다
'Spring Boot > etc' 카테고리의 다른 글
| [Spring boot 3.x] Spring boot는 왜 자동 설정을 만들었나? (0) | 2025.12.02 |
|---|---|
| Spring boot 3.0 Pre-Update 내용 정리 (1) | 2022.11.14 |
| [Spring boot] FilterRegistrationBean - url 패턴으로 Filter 설정 (0) | 2022.08.10 |
| [Spring boot] url query string 안 보이게 처리하기 (0) | 2022.08.09 |
| [Spring Boot] profile 지정하여 실행하고자 할때 (0) | 2022.02.16 |