Skip to content

FastAPI特有の落とし穴

FastAPI特有の落とし穴と、他言語との違いを詳しく解説します。

1. トランザクション境界は明示的か?

Section titled “1. トランザクション境界は明示的か?”

特徴:

# FastAPI: 明示的なトランザクション境界
from sqlalchemy.orm import Session
async def create_order(db: Session, order_data: OrderData) -> Order:
order = Order(**order_data.dict())
db.add(order)
db.commit() # 明示的なコミット
return order

他言語との比較:

// Java: 宣言的トランザクション管理
@Transactional
public Order createOrder(OrderData orderData) {
Order order = orderRepository.save(new Order(orderData));
return order;
}

落とし穴:

  • トランザクションの見落とし: トランザクションを忘れると、データの整合性が保たれない
  • 手動ロールバック: エラー時に手動でロールバックする必要がある

特徴:

# FastAPI: async/awaitによる非同期処理
@app.post("/orders")
async def create_order(order_data: OrderData):
async with httpx.AsyncClient() as client:
response = await client.post("https://api.example.com/charge", json=order_data.dict())
return response.json()

他言語との比較:

// Node.js: Promise/async-await
async function createOrder(orderData: OrderData) {
const response = await fetch('https://api.example.com/charge', {
method: 'POST',
body: JSON.stringify(orderData),
});
return await response.json();
}

落とし穴:

  • 同期処理の混在: 非同期関数内で同期I/O操作を使用すると、イベントループがブロックされる
  • エラーハンドリング: 適切なエラーハンドリングが必要

特徴:

# FastAPI: 再実行は手動で実装する必要がある
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
async def charge_payment(order_id: int, amount: float):
async with httpx.AsyncClient() as client:
response = await client.post("https://payment-api.example.com/charge", json={
"orderId": order_id,
"amount": amount,
})
return response.json()

他言語との比較:

// Java: @Retryableアノテーションで自動リトライ
@Retryable(maxAttempts = 3)
public PaymentResult chargePayment(Long orderId) {
return paymentApiClient.chargePayment(orderId);
}

落とし穴:

  • 再実行の実装漏れ: 再実行ロジックを実装し忘れると、一時的なエラーで処理が失敗する
  • 冪等性の確保: 再実行時に冪等性を確保する必要がある

4. after_commit的な逃げ道があるか?

Section titled “4. after_commit的な逃げ道があるか?”

FastAPIのトランザクションコミット後処理

Section titled “FastAPIのトランザクションコミット後処理”

特徴:

# FastAPI: トランザクションコミット後の処理は手動で実装
from sqlalchemy.orm import Session
from sqlalchemy import event
async def create_order(db: Session, order_data: OrderData) -> Order:
order = Order(**order_data.dict())
db.add(order)
db.commit()
# トランザクションコミット後に外部APIを呼ぶ
asyncio.create_task(charge_payment(order.id, order_data.amount))
return order

他言語との比較:

# Ruby (Rails): after_commitコールバック
class Order < ApplicationRecord
after_commit :call_external_api
def call_external_api
ExternalApi.call(self.id)
end
end

落とし穴:

  • トランザクションコミット後の処理: トランザクションコミット後の処理が失敗した場合、ロールバックできない
  • 一貫性の保証: トランザクションコミット後の処理が失敗した場合、データの不整合が発生する可能性がある

FastAPI特有の落とし穴のポイント:

  • トランザクション境界: 明示的なトランザクション境界、見落としに注意
  • 非同期処理: async/awaitによる非同期処理、同期処理の混在に注意
  • 再実行: 手動で実装する必要がある、冪等性の確保が必要
  • after_commit的な逃げ道: トランザクションコミット後の処理は手動で実装、一貫性の保証が必要

これらの落とし穴を理解することで、より安全なFastAPIアプリケーションを構築できます。