OpenAPI 콜백
OpenAPI 콜백 (OpenAPI Callbacks)
여러분이 만든 API가 외부의 다른 API(아마도 여러분 API를 이용하는 그 개발자가 만든 것)에 요청을 보내는 상황을 상상해 볼까요. 이런 흐름을 "콜백(callback)"이라고 불러요. 외부 개발자가 작성한 소프트웨어가 여러분 API에 요청을 보내고, 그러면 여러분 API가 다시 콜백하며 외부 API(보통 같은 개발자가 만든)에 요청을 보내는 거예요.
이런 경우, 그 외부 API가 어떻게 생겼어야 하는지 문서로 남기고 싶을 수 있어요. 어떤 _path operation_이 있어야 하는지, 어떤 body를 받아야 하는지, 어떤 response를 돌려줘야 하는지 등을 말이죠.
출처: 공식문서
콜백이 있는 앱
예시로 흐름을 살펴볼게요. 여러분이 인보이스(계산서)를 생성하는 앱을 만든다고 상상해 봐요.
이 인보이스에는 id와 title(선택), customer, total이 있습니다.
여러분 API의 사용자(외부 개발자)는 POST 요청으로 여러분 API에 인보이스를 생성해요.
그러면 여러분 API는 (상상해 보면):
- 그 인보이스를 외부 개발자의 고객에게 보내고
- 비용을 수금하고
- API 사용자(외부 개발자)에게 알림을 다시 보냅니다.
- 이 알림은 (여러분 API에서) 그 외부 개발자가 제공한 어떤 외부 API로 POST 요청을 보내는 방식으로 이뤄져요. 이것이 바로 "콜백"입니다.
일반적인 FastAPI 앱
콜백을 추가하기 전의 일반적인 API 앱이 어떤 모습인지 먼저 볼게요.
Invoice body를 받고, 콜백 URL이 담길 쿼리 파라미터 callback_url을 갖는 _path operation_이 있습니다.
이 부분은 꽤 평범해서, 코드 대부분이 이미 익숙할 거예요:
from fastapi import APIRouter, FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Invoice(BaseModel):
id: str
title: str | None = None
customer: str
total: float
class InvoiceEvent(BaseModel):
description: str
paid: bool
class InvoiceEventReceived(BaseModel):
ok: bool
invoices_callback_router = APIRouter()
@invoices_callback_router.post(
"{$callback_url}/invoices/{$request.body.id}", response_model=InvoiceEventReceived
)
def invoice_notification(body: InvoiceEvent):
pass
@app.post("/invoices/", callbacks=invoices_callback_router.routes)
def create_invoice(invoice: Invoice, callback_url: HttpUrl | None = None):
"""
Create an invoice.
This will (let's imagine) let the API user (some external developer) create an
invoice.
And this path operation will:
* Send the invoice to the client.
* Collect the money from the client.
* Send a notification back to the API user (the external developer), as a callback.
* At this point is that the API will somehow send a POST request to the
external API with the notification of the invoice event
(e.g. "payment successful").
"""
# Send the invoice, collect the money, send the notification (the callback)
return {"msg": "Invoice received"}
팁:
callback_url쿼리 파라미터는 Pydantic의 Url 타입을 사용해요.
새로 등장한 건 _path operation 데코레이터의 인자로 전달한 callbacks=invoices_callback_router.routes 하나뿐이에요. 이게 뭔지 바로 이어서 볼게요.
콜백 문서화하기
실제 콜백 코드는 여러분 API 앱에 따라 많이 달라져요.
앱마다 크게 달라지겠죠. 한두 줄짜리 코드일 수도 있어요:
callback_url = "https://example.com/api/v1/invoices/events/"
httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})
하지만 콜백에서 가장 중요한 부분은, 여러분 API가 콜백의 request body에 보낼 데이터에 맞춰서 API 사용자(외부 개발자)가 외부 API를 올바르게 구현하게 만드는 일일 거예요.
그래서 이제 할 일은, 그 외부 API가 여러분 API의 콜백을 받으려면 어떻게 생겨야 하는지 문서화하는 코드를 추가하는 거예요.
그 문서는 여러분 API의 /docs에 있는 Swagger UI에 나타나고, 외부 개발자들이 외부 API를 어떻게 만들지 알 수 있게 해 줍니다.
이 예시는 콜백 자체(그건 한 줄 코드면 충분할 수 있어요)를 구현하지 않고, 문서화 부분만 다룹니다.
팁: 실제 콜백은 그냥 HTTP 요청 하나예요. 콜백을 직접 구현할 때는 HTTPX나 Requests 같은 걸 쓰면 됩니다.
콜백 문서화 코드 작성하기
이 코드는 여러분 앱에서 실행되지 않아요. 그 외부 API가 어떻게 생겨야 하는지 문서화하기 위한 것뿐이죠.
그런데 여러분은 이미 FastAPI로 API 문서를 손쉽게 자동 생성하는 방법을 알고 있어요.
그러니 그 지식을 그대로 사용해서 외부 API가 어떻게 생겨야 하는지 문서화하면 됩니다... 외부 API가 구현해야 할 path operation(여러분 API가 호출할 것)을 만들면 돼요.
팁: 콜백 문서화 코드를 쓸 때는, 여러분이 그 외부 개발자라고 상상해 보면 도움이 돼요. 지금 여러분 API가 아니라 외부 API를 구현하고 있다고 말이죠. 이 관점(외부 개발자 입장)을 잠깐 취하면, 파라미터나 body·response용 Pydantic 모델을 어디에 둬야 할지가 더 명확하게 느껴져요.
콜백 APIRouter 만들기
먼저 콜백을 하나 이상 담을 새 APIRouter를 만듭니다.
from fastapi import APIRouter, FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Invoice(BaseModel):
id: str
title: str | None = None
customer: str
total: float
class InvoiceEvent(BaseModel):
description: str
paid: bool
class InvoiceEventReceived(BaseModel):
ok: bool
invoices_callback_router = APIRouter()
@invoices_callback_router.post(
"{$callback_url}/invoices/{$request.body.id}", response_model=InvoiceEventReceived
)
def invoice_notification(body: InvoiceEvent):
pass
@app.post("/invoices/", callbacks=invoices_callback_router.routes)
def create_invoice(invoice: Invoice, callback_url: HttpUrl | None = None):
"""
Create an invoice.
This will (let's imagine) let the API user (some external developer) create an
invoice.
And this path operation will:
* Send the invoice to the client.
* Collect the money from the client.
* Send a notification back to the API user (the external developer), as a callback.
* At this point is that the API will somehow send a POST request to the
external API with the notification of the invoice event
(e.g. "payment successful").
"""
# Send the invoice, collect the money, send the notification (the callback)
return {"msg": "Invoice received"}
콜백 path operation 만들기
콜백 _path operation_을 만들 때는 위에서 만든 APIRouter를 그대로 사용해요.
일반적인 FastAPI _path operation_처럼 보이면 됩니다:
- 받아야 할 body를 선언해야 하고, 예:
body: InvoiceEvent - 돌려줘야 할 response도 선언할 수 있고, 예:
response_model=InvoiceEventReceived
from fastapi import APIRouter, FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Invoice(BaseModel):
id: str
title: str | None = None
customer: str
total: float
class InvoiceEvent(BaseModel):
description: str
paid: bool
class InvoiceEventReceived(BaseModel):
ok: bool
invoices_callback_router = APIRouter()
@invoices_callback_router.post(
"{$callback_url}/invoices/{$request.body.id}", response_model=InvoiceEventReceived
)
def invoice_notification(body: InvoiceEvent):
pass
@app.post("/invoices/", callbacks=invoices_callback_router.routes)
def create_invoice(invoice: Invoice, callback_url: HttpUrl | None = None):
"""
Create an invoice.
This will (let's imagine) let the API user (some external developer) create an
invoice.
And this path operation will:
* Send the invoice to the client.
* Collect the money from the client.
* Send a notification back to the API user (the external developer), as a callback.
* At this point is that the API will somehow send a POST request to the
external API with the notification of the invoice event
(e.g. "payment successful").
"""
# Send the invoice, collect the money, send the notification (the callback)
return {"msg": "Invoice received"}
일반적인 _path operation_과 다른 점은 두 가지예요:
- 실제 코드가 필요 없어요. 여러분 앱은 이 코드를 절대 호출하지 않으니까요. 그저 _외부 API_를 문서화하는 데만 써요. 그래서 함수는 그냥
pass만 있으면 됩니다. - _path_에 OpenAPI 3 표현식(expression)이 들어갈 수 있어요(아래에서 더 볼게요). 여기서 여러분 _API로 보낸 원래 요청의 파라미터와 일부를 변수로 사용할 수 있습니다.
콜백 path 표현식
콜백 _path_는 여러분 _API로 보낸 원래 요청의 일부를 담을 수 있는 OpenAPI 3 표현식을 가질 수 있어요.
이 경우에는 이 str이에요:
"{$callback_url}/invoices/{$request.body.id}"
그래서 만약 API 사용자(외부 개발자)가 여러분 _API에게 이렇게 요청을 보내면:
https://yourapi.com/invoices/?callback_url=https://www.external.org/events
이런 JSON body와 함께:
{
"id": "2expen51ve",
"customer": "Mr. Richie Rich",
"total": "9999"
}
여러분 _API는 인보이스를 처리하고, 나중에 어떤 시점에 callback_url(그 _external API)로 콜백 요청을 보내요:
https://www.external.org/events/invoices/2expen51ve
이런 내용의 JSON body와 함께:
{
"description": "Payment celebration",
"paid": true
}
팁:
callbacks=에 라우터 자체(invoices_callback_router)를 넘기는 게 아니라 그.routes, 즉invoices_callback_router.routes를 넘긴다는 점에 주목하세요. FastAPI는 이 라우트들을 사용해서 콜백 OpenAPI 문서를 생성해요.
문서 확인하기
이제 앱을 실행하고 http://127.0.0.1:8000/docs로 가 볼게요.
여러분 _path operation_에 대한 "Callbacks" 섹션이 있어서 외부 API가 어떻게 생겨야 하는지 보여주는 걸 확인할 수 있어요.