SDK 생성하기

SDK 생성하기 (Generating SDKs)

FastAPIOpenAPI 명세에 기반하기 때문에, 그 API를 많은 도구가 이해하는 표준 형식으로 기술할 수 있어요.

이 덕분에 최신 상태를 유지하는 문서, 여러 언어의 클라이언트 라이브러리(SDKs), 그리고 코드와 동기화 상태를 유지하는 테스트자동화 워크플로우를 쉽게 생성할 수 있어요.

이 가이드에서는 FastAPI 백엔드용 TypeScript SDK를 생성하는 법을 배워요.

출처: 공식문서

오픈소스 SDK 생성기

다재다능한 옵션으로는 OpenAPI Generator가 있어요. 많은 프로그래밍 언어를 지원하고 OpenAPI 명세에서 SDK를 생성할 수 있죠.

TypeScript 클라이언트에는 Hey API가 목적에 맞게 설계된 솔루션이에요. TypeScript 생태계에 최적화된 경험을 제공해요.

OpenAPI.Tools에서 더 많은 SDK 생성기를 찾을 수 있어요.

FastAPI는 자동으로 OpenAPI 3.1 명세를 생성해요. 그래서 사용하는 어떤 도구든 이 버전을 지원해야 해요.

TypeScript SDK 만들기

간단한 FastAPI 애플리케이션부터 시작해 볼게요.

Python 3.10+

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    price: float


class ResponseMessage(BaseModel):
    message: str


@app.post("/items/", response_model=ResponseMessage)
async def create_item(item: Item):
    return {"message": "item received"}


@app.get("/items/", response_model=list[Item])
async def get_items():
    return [
        {"name": "Plumbus", "price": 3},
        {"name": "Portal Gun", "price": 9001},
    ]

path operationsItemResponseMessage 모델을 사용해서 요청 페이로드와 응답 페이로드에 쓰는 모델들을 정의하고 있다는 점에 주목하세요.

API 문서

/docs로 가면 요청에 보내고 응답으로 받을 데이터에 대한 스키마가 있다는 걸 볼 수 있어요.

그 스키마들을 볼 수 있는 이유는 앱의 모델들로 선언됐기 때문이에요.

그 정보는 앱의 OpenAPI 스키마에 있고, API 문서에 표시돼요.

OpenAPI에 포함된 모델들의 그 정보는 바로 클라이언트 코드를 생성하는 데 사용될 수 있는 것이에요.

Hey API

모델이 있는 FastAPI 앱이 준비되면, Hey API로 TypeScript 클라이언트를 생성할 수 있어요. 가장 빠른 방법은 npx를 쓰는 거예요.

npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client

이렇게 하면 ./src/client에 TypeScript SDK가 생성돼요.

@hey-api/openapi-ts 설치 방법생성된 출력에 대해서는 그들의 웹사이트에서 배울 수 있어요.

SDK 사용하기

이제 클라이언트 코드를 임포트해서 사용할 수 있어요. 이렇게 생겼을 수 있는데, 메서드에 대한 자동 완성(autocompletion)을 받는다는 점에 주목하세요.

보낼 페이로드에 대한 자동 완성도 받아요.

nameprice에 대한 자동 완성에 주목하세요. FastAPI 애플리케이션에서 Item 모델에 정의된 것들이에요.

보내는 데이터에 대한 인라인 오류(inline errors)도 생겨요.

응답 객체에도 자동 완성이 있어요.

태그가 있는 FastAPI 앱

여러 경우에 FastAPI 앱은 더 커지고, path operations의 다른 그룹들을 나누기 위해 태그를 사용하게 될 거예요.

예를 들어 items 섹션과 users 섹션이 있고, 태그로 나눠질 수 있어요.

Python 3.10+

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    price: float


class ResponseMessage(BaseModel):
    message: str


class User(BaseModel):
    username: str
    email: str


@app.post("/items/", response_model=ResponseMessage, tags=["items"])
async def create_item(item: Item):
    return {"message": "Item received"}


@app.get("/items/", response_model=list[Item], tags=["items"])
async def get_items():
    return [
        {"name": "Plumbus", "price": 3},
        {"name": "Portal Gun", "price": 9001},
    ]


@app.post("/users/", response_model=ResponseMessage, tags=["users"])
async def create_user(user: User):
    return {"message": "User received"}

태그가 있는 TypeScript 클라이언트 생성하기

태그를 사용하는 FastAPI 앱의 클라이언트를 생성하면, 보통 클라이언트 코드도 태그에 기반해 나눠져요.

이렇게 하면 클라이언트 코드에서 항목들을 올바르게 정렬하고 그룹화할 수 있어요.

이 경우 다음을 갖게 돼요.

  • ItemsService
  • UsersService

클라이언트 메서드 이름

지금은 createItemItemsPost 같은 생성된 메서드 이름이 그리 깔끔해 보이지 않아요.

ItemsService.createItemItemsPost({name: "Plumbus", price: 5})

...이건 클라이언트 생성기가 각 path operation 에 대해 OpenAPI 내부 operation ID를 사용하기 때문이에요.

OpenAPI는 각 operation ID가 모든 path operations 에서 유일(unique)해야 한다고 요구해요. 그래서 FastAPI는 함수 이름, 경로, HTTP 메서드/오퍼레이션을 사용해서 그 operation ID를 생성해요. 그렇게 해야 operation ID가 유일함을 보장할 수 있으니까요.

하지만 다음에 그걸 개선하는 법을 보여드릴게요. 🤓

커스텀 Operation ID와 더 나은 메서드 이름

이 operation ID가 생성되는 방식을 수정해서 더 단순하게 만들고 클라이언트에서 더 단순한 메서드 이름을 갖게 할 수 있어요.

이 경우 각 operation ID가 다른 방식으로 유일함을 보장해야 해요.

예를 들어 각 path operation 이 태그를 갖도록 하고, 태그path operation 이름(함수 이름)에 기반해 operation ID를 생성할 수 있어요.

커스텀 유일 ID 함수

FastAPI는 각 path operation 에 대해 유일 ID를 사용해요. 이 ID는 operation ID와 요청이나 응답에 필요한 커스텀 모델들의 이름에도 사용돼요.

그 함수를 커스터마이즈할 수 있어요. APIRoute를 받아 문자열을 출력해요.

예를 들어 여기서는 첫 번째 태그(아마 하나만 있을 테니)와 path operation 이름(함수 이름)을 사용하고 있어요.

그런 다음 그 커스텀 함수를 generate_unique_id_function 파라미터로 FastAPI에 전달할 수 있어요.

Python 3.10+

from fastapi import FastAPI
from fastapi.routing import APIRoute
from pydantic import BaseModel


def custom_generate_unique_id(route: APIRoute):
    return f"{route.tags[0]}-{route.name}"


app = FastAPI(generate_unique_id_function=custom_generate_unique_id)


class Item(BaseModel):
    name: str
    price: float


class ResponseMessage(BaseModel):
    message: str


class User(BaseModel):
    username: str
    email: str


@app.post("/items/", response_model=ResponseMessage, tags=["items"])
async def create_item(item: Item):
    return {"message": "Item received"}


@app.get("/items/", response_model=list[Item], tags=["items"])
async def get_items():
    return [
        {"name": "Plumbus", "price": 3},
        {"name": "Portal Gun", "price": 9001},
    ]


@app.post("/users/", response_model=ResponseMessage, tags=["users"])
async def create_user(user: User):
    return {"message": "User received"}

커스텀 Operation ID를 가진 TypeScript 클라이언트 생성하기

이제 클라이언트를 다시 생성하면, 개선된 메서드 이름이 있다는 걸 볼 수 있어요.

보시다시피 메서드 이름 이제 태그 다음에 함수 이름을 갖고 있어요. URL 경로와 HTTP 오퍼레이션의 정보는 더 이상 포함하지 않아요.

클라이언트 생성기를 위한 OpenAPI 명세 전처리하기

생성된 코드에는 여전히 약간의 중복 정보가 있어요.

이 메서드가 items와 관련 있다는 건 이미 알고 있어요. 그 단어가 ItemsService(태그에서 가져온)에 있으니까요. 하지만 여전히 메서드 이름 앞에도 태그 이름이 붙어 있어요. 😕

일반적으로 OpenAPI를 위해서는 그걸 유지하고 싶을 거예요. operation ID가 유일함을 보장해 주니까요.

하지만 생성된 클라이언트를 위해서는, 클라이언트를 생성하기 직전에 OpenAPI operation ID를 수정해서 메서드 이름을 더 보기 좋고 깔끔하게 만들 수 있어요.

OpenAPI JSON을 openapi.json 파일로 다운로드하고, 이 스크립트처럼 그 태그 접두사를 제거할 수 있어요.

Python 3.10+

import json
from pathlib import Path

file_path = Path("./openapi.json")
openapi_content = json.loads(file_path.read_text())

for path_data in openapi_content["paths"].values():
    for operation in path_data.values():
        tag = operation["tags"][0]
        operation_id = operation["operationId"]
        to_remove = f"{tag}-"
        new_operation_id = operation_id[len(to_remove) :]
        operation["operationId"] = new_operation_id

file_path.write_text(json.dumps(openapi_content))

이렇게 하면 operation ID가 items-get_items 같은 것에서 그냥 get_items로 바뀌어서, 클라이언트 생성기가 더 단순한 메서드 이름을 생성할 수 있어요.

전처리된 OpenAPI로 TypeScript 클라이언트 생성하기

최종 결과물이 이제 openapi.json 파일에 있으니, 입력 위치를 업데이트해야 해요.

npx @hey-api/openapi-ts -i ./openapi.json -o src/client

새 클라이언트를 생성한 후에는 이제 깔끔한 메서드 이름을 갖게 되고, 자동 완성, 인라인 오류 등을 모두 받아요.

장점 (Benefits)

자동 생성된 클라이언트들을 사용하면 다음에 대해 자동 완성을 받아요.

  • 메서드.
  • 본문의 요청 페이로드, 쿼리 파라미터 등.
  • 응답 페이로드.

모든 것에 대해 인라인 오류도 받아요.

그리고 백엔드 코드를 업데이트할 때마다, 프론트엔드를 재생성하면 새 path operations이 메서드로 제공되고, 옛것들은 제거되며, 다른 변경사항도 생성된 코드에 반영돼요. 🤓

이것은 또한 뭔가가 바뀌면 그것이 클라이언트 코드에 자동으로 반영된다는 뜻이에요. 그리고 클라이언트를 빌드하면, 사용된 데이터에 불일치(mismatch) 가 있으면 오류가 나요.

그래서 최종 사용자에게 프로덕션에서 오류로 나타나길 기다렸다가 그 문제가 어디인지 디버깅하는 대신, 개발 주기 훨씬 초기에 많은 오류를 감지할 수 있어요. ✨

더 알아보기 (Learn more)

이 문서는 FastAPI 공식 문서 - Generating SDKs를 한국어로 정리한 번역이에요. 원문에서 최신 내용과 더 다양한 예시를 확인하세요.