[FastAPI X Python] fastAPI router 옵션 살펴보기

봄도둑·2024년 9월 3일
0

FastAPI X Python

목록 보기
1/2

앞으로 몇 가지 아티클은 사내에서 python과 fastAPI 도입을 하면서 겪은 일과 배운 것들을 정리 해보려고 합니다. java, spring 진영에서 개발하면서 자바의 매력을 충분히 느꼈습니다만, 회사의 기술 바운더리도 확장 시킴과 동시에 제가 활용할 수 있는 기술의 폭을 많이 넓힐 수 있는 경험을 쌓고 싶었습니다.
그 결과, 8월 중순부터 시작된 신규 프로젝트는 fastAPI를 적용을 제안하고 개발에 들어갔습니다. 본격적으로 개발한지는 일주일 정도 되었지만 spring과는 다른 맛의 굉장히 흥미로운 경험을 하고 있습니다.
java에서 python 개발을 시도하면서 겪은 시행착오들, 한국어로 찾기 힘든 fastAPI와 관련된 다양한 레퍼런스들을 공유 해보고자 합니다.
특히, 앞으로 FastAPI X Python 시리즈가 새로운 개발을 배워보고 싶은 주니어 개발자들에게 좋은 영향을 주었으면 합니다!

1. APIRouter class

FastAPI는 spring과는 달리 복잡한 절차 없이 바로 프로젝트를 생성하고 구동할 수 있습니다. 프로젝트의 구조, 설계도 특정 구현 패턴에 지나치게 의존하지 않고 개발자의 설계에 따라 다양한 모습을 취할 수 있습니다.

예시로 쇼핑몰 백엔드 서버를 구현한다고 가정하고 설명 하겠습니다.

최초의 fastAPI 구동 코드를 한 번 살펴보겠습니다.

from fastapi import FastAPI

app = FastAPI()

@app.get("/health")
def health_check_handler():
    return {"status": "ok"}

그리고 해당 코드가 작성된 python 파일을 실행하거나 uvicorn을 이용해 실행만 하면 서버가 바로 생성됩니다. spring보다는 최초 구동 시 사용자가 작성해야할 부가 코드 양이 압도적으로 적습니다.

python을 다룰 줄 알면 누구나 쉽게 웹 서버를 만들 수 있도록 만든 것이 바로 fastAPI입니다.

그렇다면 우리가 만들어야 할 것은 쇼핑몰의 백엔드 서버일 것입니다. 그렇다면 우리가 정의하고 구현해야할 기능 몇 가지를 추려보겠습니다.

  • 사용자(User)
    • 신규 사용자 가입
    • 사용자 정보 조회
    • 사용자의 장바구니 조회
    • 사용자의 구매 내역 조회
    • 사용자 개인 정보 변경
  • 주문(Order)
    • 상품 주문
    • 결제 요청
  • 상품(Product)
    • 상품 목록 조회
    • 상품 단일 조회

추려낸 기능을 기준으로 보자면 크게 3가지의 분류를 가질 수 있고 이에 따라 해당 기능들을 수행할 API들을 정의할 수 있습니다.

그런데 이렇게 많은 기능들이 하나의 main.py에 작성된다면 어떠한 모습일까요?

from fastapi import FastAPI

app = FastAPI()

@app.get("/health")
def health_check_handler():
    return {"status": "ok"}
    
@app.post("users")
def join_handler():
    #...

@app.get("users")
def get_user_handler():
    #...

@app.get("users/cart/list")
def get_cart_list_handler():
    #...

@app.post("order")
def order_handler():
    #...
    
@app.post("order/payment")
def pay_handler():
    #...
    
@app.get("product/list")
def get_product_list_handler():
    #...

main.py에는 모든 API들이 정의되어 있게 됩니다. 하나의 큰 기능에 따라 분류 되지 않고 여러 기능이 뒤섞여 있는 상태로 다양한 관심사를 다루는 main.py는 비대해질 수 밖에 없으며 모든 구현 코드들은 main.py가 의존하게 되는 현상이 발생합니다.

그렇기 때문에 우리는 응집도를 높이고 결합도를 낮추기 위해 main.py를 관심사에 맞게 잘게 잘라내는 작업을 수행합니다.

from fastapi import FastAPI
from api import user, order, product

app = FastAPI()

app.include_router(user.router)
app.include_router(order.router)
app.include_router(product.router)

이 작업에서 각각의 기능에 대한 관심은 APIRouter에 의해 하나의 작은 또 하나의 fastAPI 애플리케이션처럼 동작하게 됩니다. 아래의 예제 코드로 보면 user, order, product 세 개가 서로 다른 작은 단위의 애플리케이션으로 구현된 것을 볼 수 있습니다. (편의상 하나의 코드 블럭에 작성했습니다.)

# user.py
from fastapi import APIRouter

router = APIRouter(prefix="/users")

@router.get("")
def join_handler():
    #...
    
# order.py
from fastapi import APIRouter

router = APIRouter(prefix="/order")

@router.post("")
def order_handler():
    #...

# product.py
from fastapi import APIRouter

router = APIRouter(prefix="/product")

@router.get("/list")
def get_product_list_handler():
    #...

각 기능들은 APIRouter를 통해 분리 되었으며 APIRouter에서 제공하는 기능은 최상위 레벨의 FastAPI()와 동일한 기능을 제공합니다.

이렇게 APIRouter를 통한 분기는 spring의 관점으로 대입해보면 controller에 대응한다고 볼 수 있겠습니다. spring에서는 @RestController 라는 어노테이션을 통해 bean으로 등록해 사용할 수 있다고 볼 수 있습니다.

APIRouter가 제공하는 다양한 옵션은 spring의 어노테이션에 대응하며 사용할 수 있습니다. 이제 APIRouter에서 가장 많이 사용하는 옵션들을 살펴봅시다.

2. 코드 동작 관여

APIRouter에서 제공하는 몇 가지 옵션은 해당 코드의 실행을 보조해주거나 경로를 지정하는 역할을 수행합니다.

이를 살펴보기 위해 이미 구현된 간단한 handler 메소드 하나를 살펴 보겠습니다. 토이 프로젝트로 구현한 관리자에 의해 게시글이 승인되는 handler입니다.

@router.put(
    "/publication",
    status_code=200,
    dependencies=[Depends(auth_admin)],
    summary="게시글 승인 API",
    description="게시글 id 리스트에 해당하는 게시글을 approved 상태로 변경합니다.",
    response_model=MultipleUpdateResponse
)
def publish_post_handler(
        request: HubPostIdListRequest,
        admin_id: Annotated[str | None, Header(alias='admin_id')] = None,
        user_repo: UserRepository = Depends(),
        post_repo: PostRepository = Depends(),
) -> MultipleUpdateResponse:
		
		# 게시글 승인 로직 동작
		
    return MultipleUpdateResponse(
        request_count=len(request.id_list),
        updated_count=update_count
    )
  • path : 데코레이터된 handler에 접근할 수 있는 url 경로를 의미합니다. 넘겨줄 파라미터의 이름을 지정하지 않았다면 넘겨준 인자의 가장 첫번째 문자열 인자가 경로를 의미합니다. spring으로 대입해보면 RequestMapping에 대응한다고 볼 수 있습니다. 예시의 코드에서는 "/publication" 인자가 여기에 해당합니다.
  • status_code : handler가 정상적으로 동작을 수행했을 때 내려주는 response status code입니다. 따로 지정하지 않았다면 200을 기본값으로 설정하고 있습니다.
  • dependencies :해당 handler에 종속성을 선언합니다.
    • dependencies는 FastAPI(), APIRouter() 그리고 개별 handler에 적용 가능한 항목입니다. FastAPI는 전역, APIRouter는 router 레벨, handler는 handler 단일 메소드 레벨에서 적용하게 됩니다.
    • 종속성은 주로 동일한 코드가 반복되거나 동일한 DB 연결, 보안 및 인증 등 코드의 반복을 처리할 수 있습니다. spirng 개념에서는 횡단 관심사를 처리하는 AOP의 관점으로도 볼 수 있습니다.
    • 입력 받은 값은 종속성을 배열로 넘겨 여러 개의 종속성을 선언할 수 있습니다. 위의 예시 코드에서는 auth_admin 를 종속성으로 선언해 해당 handler가 실행 되기 전에 auth_admin 를 통해 인증 및 인가를 처리하도록 했습니다.
    • FastAPI의 dependencies(종속성)은 깊게 파보면 굉장히 재미있는 영역이지만 오늘은 router에 대한 소개이기 때문에 다른 글에서 다루겠습니다.

3. Swagger 문서에서의 사용

spring에서 swagger를 사용하기 위해서는 swagger dependency를 추가하고 swagger에 대한 설정을 진행했습니다만, FastAPI는 swagger에 대한 dependency를 사용자가 선언하지 않아도 /docs 경로를 통해 swagger 문서를 볼 수 있습니다.

다만 개인적으로는 spring에서 swagger를 명확하게 작성하려면 다양한 어노테이션을 붙여주어야 하는데 코드 상에서 보이는 모습이 너무 지저분하다는 느낌을 많이 받았습니다. 반면, FastAPI는 인자 값 몇 개를 선언해주는 것만으로도 간단하게 swagger 문서의 정의와 표현을 변경할 수 있었습니다.

다시 한 번 동일한 예제 코드를 살펴 보겠습니다.

@router.put(
    "/publication",
    status_code=200,
    dependencies=[Depends(auth_admin)],
    summary="게시글 승인 API",
    description="게시글 id 리스트에 해당하는 게시글을 approved 상태로 변경합니다.",
    response_model=MultipleUpdateResponse
)
def publish_post_handler(
        request: HubPostIdListRequest,
        admin_id: Annotated[str | None, Header(alias='admin_id')] = None,
        user_repo: UserRepository = Depends(),
        post_repo: PostRepository = Depends(),
) -> MultipleUpdateResponse:
		
		# 게시글 승인 로직 동작
		
    return MultipleUpdateResponse(
        request_count=len(request.id_list),
        updated_count=update_count
    )
  • summary : swagger 상에서 API의 요약 정보를 표기할 때 사용합니다.
  • description : swagger 상에서 API의 상세 정보를 표기할 때 사용합니다.
  • response_model : API 응답에 사용하는 Object class를 지정합니다. handler의 return 타입을 명시 해주었다면 해당 항목은 작성하지 않아도 명시한 return 타입에 따라 response_model이 지정됩니다.
  • responses : 예시 코드에는 없으나 생각보다 자주 사용하는 파라미터입니다. response_model 외에도 응답할 수 있는 model로 주로 에러가 발생했을 때의 object를 작성합니다.

summary와 description은 아래 이미지의 빨간색 밑줄과 파란색 밑줄에 대응합니다.

마무리

이 외에도 다양한 옵션을 지정할 수 있습니다. API 완료 후의 callback에 대한 정보를 담을 수 있는 callbacks , 응답에 대한 상세 설명을 작성할 수 있는 response_description 등 다양한 옵션을 제공합니다.

조금 더 다양한 옵션이 궁금하다면 @router.put 의 참조하고 있는 fastapi.routing.py를 보시면 상세하게 설명되어 있습니다! 다른 라이브러리에 비해 공식 문서와 작성된 주석이 상세하게 쓰여져 있어 별도의 레퍼런스를 찾지 않아도 양질의 학습을 할 수 있습니다.


*REFERENCES

profile
Java Spring 백엔드 개발자입니다. java 외에도 다양하고 흥미로운 언어와 프레임워크를 학습하는 것을 좋아합니다.

0개의 댓글