반응형
FastAPI 예제는 보통 이렇게 시작한다.
from fastapi import FastAPI
app = FastAPI()
근데 프로젝트가 조금만 커져도 곧바로 이런 고민이 생긴다.
- 설정(Settings) 주입은 어디서 하지?
- DB 엔진/세션은 언제 만들지?
- 테스트에서 “설정을 바꿔서 app를 새로 만들고 싶다”면?
그래서 나는 create_app() 팩토리 패턴으로 시작했다.
결론부터 말하면: 테스트/환경별 설정/초기화 순서 관리가 훨씬 편해진다.
핵심 코드: create_app() + lifespan
doubles-1 서버의 진입점은 대략 이런 구조다.
def create_app(settings: Settings | None = None) -> FastAPI:
app_settings = settings or get_settings()
@asynccontextmanager
async def lifespan(app: FastAPI):
engine = get_engine(app.state.settings)
app.state.engine = engine
init_db(engine=engine)
yield
app = FastAPI(title=app_settings.app_name, lifespan=lifespan)
app.state.settings = app_settings
app.include_router(root_router)
app.include_router(api_router, prefix=app_settings.api_v1_prefix)
return app
app = create_app()
여기서 학습 포인트는 딱 3개.
1) app factory가 좋은 이유(특히 테스트)
create_app(settings)처럼 만들어두면 테스트에서 이렇게 할 수 있다.
- “테스트용 SQLite 파일 경로”를 넣어서 app를 새로 생성
- DI override를 넣어서 외부 의존성(AI, 날씨 API)을 mock 처리
예를 들어 실제 테스트 코드가 대충 이런 느낌이다.
def _make_client(tmp_path: Path) -> TestClient:
settings = Settings(database_url=f"sqlite:///{tmp_path / 'test.db'}")
app = create_app(settings)
return TestClient(app)
이러면 “전역 app 하나”에 의존하는 구조보다 테스트가 훨씬 단단해진다.
2) lifespan은 “앱 시작/종료” 구간을 잡아준다
FastAPI는 예전에는 @app.on_event("startup")를 많이 썼는데, 요즘은 lifespan이 훨씬 깔끔하다.
여기서 내가 한 건 딱 이거다.
- 앱이 시작될 때 DB 엔진을 만들고(
get_engine) - 테이블을 만들어두고(
init_db→SQLModel.metadata.create_all) - 그 엔진을
app.state.engine에 넣어두기
중요한 포인트는 “요청 처리 전에 초기화가 끝난 상태”를 보장한다는 거.
3) app.state는 “앱 전역 컨테이너”처럼 쓴다
나는 여기에 두 개를 넣었다.
app.state.settings: Settings (환경변수에서 로드된 설정)app.state.engine: SQLAlchemy Engine (요청마다 Session 만들 때 씀)
이렇게 해두면 DI(Depends) 쪽에서 Request를 받아서 깔끔하게 꺼내 쓸 수 있다.
def get_db_session(request: Request) -> Generator[Session, None, None]:
with Session(request.app.state.engine) as session:
yield session
실습: “테스트용 설정으로 app 생성” 해보기
내가 추천하는 미니 실습은 이거.
- 테스트에서
Settings(database_url=...)를 주입해 app를 만든다 - 그 상태로
/health를 찔러서 정상 응답을 받는다
def test_health(client):
response = client.get("/health")
assert response.status_code == 200
이게 되면, 이미 네 머릿속에 “프로젝트형 FastAPI”의 뼈대가 들어온 거다.
마무리
create_app() + lifespan은 한 번 손에 익으면 다른 프로젝트에서도 계속 쓰게 된다.
특히 테스트/DI/설정 주입이랑 궁합이 너무 좋다. (이거 진짜임)
다음 편에서는 설정을 본격적으로 파본다.
.env는 어떻게 읽는지DOUBLES_prefix 같은 규칙을 왜 잡는지lru_cache로 Settings를 왜 캐싱하는지
참고
반응형