회사에서 데이터 수집용 웹소켓 서버 어플리케이션을 테스트용으로 간단하게 띄워야 하는 일이 있었다.
Spring Framework + Spring Web MVC를 사용했을 시절이었다면 @PostConstruct
을 사용해서 빈 초기화 시에 딱 한 번 웹소켓 인스턴스를 띄웠거나 실제로 사용하지는 않았지만 ApplicationListner
, 또는 @EventListener
어노테이션으로 어플리케이션 이벤트를 감지해서 동작을 처리했었을 것이다.
FastAPI에서는 이러한 역할을 하는 기능이 있는지 ChatGPT를 통해 확인했고 어렵지 않게 답변을 받아냈다.
@on_event
대신 @asynccontextmanager
ChatGPT에서는 @on_event
데코레이터를 사용하여 특정 이벤트에 실행될 로직을 작성하면 된다는 답변을 주었다.
다만 최신 자료가 아니었던 모양인지 실제로 PyCharm에서 확인했을 때는 해당 데코레이터가 deprecated 되어 다른 방법을 사용해야 한다는 것을 알려주고 있었다.
공식 문서에서는 기존 @on_event
를 대체하는 기능으로 LifeSpan
을 사용하는 것을 안내하고 있었다.
https://fastapi.tiangolo.com/advanced/events/
Lifespan은 어플리케이션 실행 시, 또는 종료 시 (종료 직전)에 실행될 수 있는 로직을 정의할 수 있는 기능이며 어플리케이션 전체 수명에 적용된다.
적용하는 방법은 상당히 간단하다.
@asynccontextmanager
async def lifespan(app: FastAPI):
# 이하 어플리케이션 시작 시 처리할 로직
task0 = asyncio.create_task(send_server_start_alarm())
await task0
task1 = asyncio.create_task(init_temp_data())
await task1
task2 = asyncio.create_task(start_websocket())
yield
# 이하 어플리케이션 종료 시 처리할 로직
task6 = asyncio.create_task(send_server_close_alarm())
await task6
task2.cancel()
task3 = asyncio.create_task(save_data_before_termination())
await task3
# app 인스턴스의 lifespan 파라미터에 위에서 지정한 lifespan 지정)
app = FastAPI(lifespan=lifespan)
공식 문서의 설명에서는 어플리케이션 실행 시 + 요청을 받기 직전에 yield 이전의 로직을 실행하고, 요청을 받은 이후 + 어플리케이션 종료 직전에 yield 이후의 로직을 실행한다고 되어 있다.
좀 헷갈리게 설명한 부분이 있지만 기존의 @on_event
의 startup, shutdown 을 대체하는 기능인만큼 어쨌든 어플리케이션 실행 시, 종료 시 한 번만 호출하는 것은 보장되어 있다.
다만 주의할 점은 비동기 함수로 선언을 해야 하기 때문에 lifespan 내에서 다른 동기 함수를 호출하거나 그밖의 AsyncIO를 통해 비동기 처리를 해야 할 경우 로직이 실행이 안 되거나 멈출 수 있으므로 비동기 처리에 대한 이해가 필요하다. (고생을 많이 했다...)
여담이지만 @on_event
데코레이터는 FastAPI에서 제공하는 스펙이지만 lifespan 및 @asynccontextmanager
는 Starlette의 스펙이다. 기회가 되면 Starlette 공식 문서를 한 번 확인해봐야겠다.