Python|TypedDictとdataclassのAPI受信構造の使い分け方
この記事のポイント
- TypedDictとdataclassの適切な責務分離をレビューで判断できる
- API受信層・業務層の型構造分離の設計方針を身につける
- 型安全性と柔軟性を両立した現場設計の指摘技法が学べる
そもそもTypedDictとdataclassとは
TypedDictとは
from typing import TypedDict
class ApiRequest(TypedDict):
request_id: int
endpoint: str
client_ip: str
- 辞書ベースの構造
- 動的データ(JSONなど)の受信・検証前処理に有効
- 辞書操作互換性を維持
dataclassとは
from dataclasses import dataclass
@dataclass
class ApiRequest:
request_id: int
endpoint: str
client_ip: str
- オブジェクト指向的構造
- 内部業務処理・ドメインモデル向き
- 型安全・補完性・リファクタリング耐性高
この2者は役割が明確に異なります。
なぜこれをレビューするのか
現場で頻発する失敗は以下の通りです。
- 全てをTypedDict化して辞書依存設計崩壊
- dataclassの利用タイミング誤認識
- API受信責務と内部処理責務が混在
- 柔軟性依存による型安全崩壊
レビューアーは「型定義=責務写像」で読み解きます。
レビューアー視点
- TypedDict導入箇所がAPI受信責務に一致しているか
- dataclass導入箇所がドメイン処理責務に限定されているか
- 変換ポイントが整理されているか
- 動的データ構造依存が業務層へ侵食していないか
- 型安全保証が各層で実現できているか
開発者視点
- TypedDictは受信層専用型として割り切る
- dataclassは処理層・ドメイン層型に限定する
- 変換責務はAPIアダプター層に集中させる
- 辞書型のまま内部業務へ流さない
- 柔軟性より安全性優先で責務整理する
良い実装例
なぜこの実装が良いのか
- TypedDictはAPI受信責務専用
- dataclassは内部処理責務専用
- 変換責務はアダプター層に明示
- 柔軟性と型安全が自然に両立
# api_schema.py
from typing import TypedDict
class ApiRequestPayload(TypedDict):
request_id: int
endpoint: str
client_ip: str
# domain_model.py
from dataclasses import dataclass
@dataclass
class ApiRequestLog:
request_id: int
endpoint: str
client_ip: str
# converter.py
def to_domain(payload: ApiRequestPayload) -> ApiRequestLog:
return ApiRequestLog(
request_id=payload["request_id"],
endpoint=payload["endpoint"],
client_ip=payload["client_ip"],
)
補足
受信層 → 変換層 → 業務層という型責務の分離が実現されています。レビューアーは変換ポイントの明示性を重点確認します。
レビュー観点
- TypedDictはAPI受信責務専用になっているか
- dataclassは内部処理責務専用になっているか
- 辞書操作が業務層へ侵食していないか
- 変換責務が明示的に管理されているか
- 柔軟性依存で型安全が崩壊していないか
良くない実装例: ケース1(業務層までTypedDict侵食)
# bad_typed_dict_leak.py
from typing import TypedDict
class ApiRequest(TypedDict):
request_id: int
endpoint: str
client_ip: str
def save_log(data: ApiRequest):
print(data["request_id"])
@Reviewer業務層まで辞書型で設計されています。内部処理はdataclass等へ責務分離してください。
問題点
- 業務層が辞書操作前提設計
- 型安全性崩壊
- 補完・リファクタリング耐性消失
改善例
# good_dataclass_domain.py
from dataclasses import dataclass
@dataclass
class ApiRequestLog:
request_id: int
endpoint: str
client_ip: str
def save_log(log: ApiRequestLog):
print(log.request_id)
辞書型は受信境界線で止めるが原則です。レビュー時は業務層が辞書操作していないかを即確認します。
良くない実装例: ケース2(dataclassで受信柔軟性崩壊)
# bad_dataclass_api_receiving.py
from dataclasses import dataclass
@dataclass
class ApiRequest:
request_id: int
endpoint: str
client_ip: str
def handle_request(data: dict):
request = ApiRequest(**data)
@Reviewer受信層でdataclass直接使用は柔軟性・検証性が落ちます。TypedDict経由で受信責務を整理してください。
問題点
- API受信段階で型固定
- 柔軟なバリデーション困難
- エラーハンドリング脆弱
改善例
# good_typed_dict_api_receiving.py
from typing import TypedDict
class ApiRequestPayload(TypedDict):
request_id: int
endpoint: str
client_ip: str
def handle_request(data: ApiRequestPayload):
# バリデーション・変換責務を分離実装
pass
TypedDictで受信→検証→変換→dataclassという段階分離設計が安定します。レビューではAPI境界線型設計を重点確認します。
良くない実装例: ケース3(型変換責務の曖昧化)
# bad_implicit_conversion.py
from typing import TypedDict
from dataclasses import dataclass
class ApiRequestPayload(TypedDict):
request_id: int
endpoint: str
client_ip: str
@dataclass
class ApiRequestLog:
request_id: int
endpoint: str
client_ip: str
def save_log(payload: ApiRequestPayload):
log = ApiRequestLog(**payload)
print(log.request_id)
@Reviewer変換責務が業務層に侵入しています。専用変換関数へ責務分離してください。
問題点
- 業務層内に辞書展開記法が混入
- 変換責務が境界線崩壊
- 可読性・保守性低下
改善例
# good_explicit_converter.py
def to_domain(payload: ApiRequestPayload) -> ApiRequestLog:
return ApiRequestLog(
request_id=payload["request_id"],
endpoint=payload["endpoint"],
client_ip=payload["client_ip"],
)
def save_log(log: ApiRequestLog):
print(log.request_id)
型変換責務は専用関数化し、境界線責務を明示が設計原則です。レビューでは変換の混在有無を即時確認します。
良くない実装例: ケース4(Optionalと柔軟辞書型の過剰依存)
# bad_overflexible_dict.py
from typing import TypedDict, Optional
class ApiRequestPayload(TypedDict, total=False):
request_id: Optional[int]
endpoint: Optional[str]
client_ip: Optional[str]
@Reviewer柔軟性依存で全フィールドOptional化しています。受信必須/任意区分を整理してください。
問題点
- total=False濫用
- None混在型設計崩壊
- 業務責務不明瞭
改善例
# good_typed_dict_mandatory_optional.py
from typing import TypedDict, Optional
class ApiRequestPayload(TypedDict):
request_id: int
endpoint: str
client_ip: str
archived_at: Optional[str]
Optionalは存在不定責務に限定が基本です。レビューではOptional乱用を即指摘対象にします。
観点チェックリスト
まとめ
TypedDictとdataclassの設計分離は「受信柔軟性 vs 内部型安全」のバランス設計技術そのものです。
レビューアーは常に
- どこまでが受信責務か?
- どこからが業務責務か?
- どこで型を切り替えているか?
を読み解き、型定義を「責務境界図」として読むレビュー力を養っていくことが重要です。
このレビュー技法は現場育成教材として極めて実戦的に機能します。