본문 바로가기

개발자 생활

FastAPI 연재 2편 — create_app + lifespan으로 부트스트랩 깔끔하게

반응형

FastAPI 예제는 보통 이렇게 시작한다.

from fastapi import FastAPI

app = FastAPI()

근데 프로젝트가 조금만 커져도 곧바로 이런 고민이 생긴다.

  • 설정(Settings) 주입은 어디서 하지?
  • DB 엔진/세션은 언제 만들지?
  • 테스트에서 “설정을 바꿔서 app를 새로 만들고 싶다”면?

그래서 나는 create_app() 팩토리 패턴으로 시작했다.
결론부터 말하면: 테스트/환경별 설정/초기화 순서 관리가 훨씬 편해진다.


핵심 코드: create_app() + lifespan

대표 이미지 (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_dbSQLModel.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를 왜 캐싱하는지

참고

“테스트 친화적 app factory” 이미지

반응형