FastAPIをClean Architecture寄りに分割していると、
Route
↓
UseCase
↓
Repository
↓
Database
という構成がよく登場します。
自分が実装していて混乱したのは、**「それぞれ何を担当するのか」と「どのオブジェクトを渡せばよいのか」**でした。
RouteはHTTPの入口
Routeでは、HTTPに関する処理を担当します。
例えば、
@router.get(
"/help/{slug}",
response_model=HelpDetail,
)
async def read_help(
slug: str,
db: AsyncSession = Depends(get_db),
):
repository = HelpRepositoryImpl(db)
use_case = HelpUseCase(repository)
return await use_case.get_help(slug)
ここでは、
- URLやHTTPメソッド
- Path / Query Parameter
- Dependency Injection
- Response Model
などを扱います。
一方、具体的なSQLをRouteへ直接書くと責務が増えてしまうため、DB処理はRepositoryへ任せます。
UseCaseは「アプリケーションとして何をするか」
UseCaseでは、
slugからヘルプ詳細を取得する
のような、アプリケーションの処理を表現します。
class HelpUseCase:
def __init__(
self,
help_repo: HelpRepository,
):
self.help_repo = help_repo
async def get_help(
self,
slug: str,
):
help_item = await (
self.help_repo.get_help_by_slug(slug)
)
return help_item
UseCase自身がSQLAlchemyのクエリを書くのではなく、必要なデータをRepositoryへ依頼します。
RepositoryはDBアクセスを担当する
Repositoryでは、SQLAlchemyを使った具体的な取得処理を書きます。
class HelpRepositoryImpl:
def __init__(
self,
session: AsyncSession,
):
self.session = session
async def get_help_by_slug(
self,
slug: str,
):
stmt = select(Help).where(
Help.slug == slug
)
return (
await self.session.scalars(stmt)
).one_or_none()
つまり、
UseCase
→ 「slugからHelpが欲しい」
Repository
→ 「SQLAlchemyでどう取得するか」
という役割分担です。
Session object has no attribute ...が出た理由
今回つまずいたのが、UseCaseへSessionを直接渡していたケースです。
# NG
use_case = HelpUseCase(db)
UseCaseは、
self.help_repo.get_help_by_slug(slug)
を呼びます。
しかしself.help_repoに入っているのはRepositoryではなくAsyncSessionです。
結果として、
AsyncSession.get_help_by_slug(...)
を呼ぼうとしてしまいます。
当然Sessionにはそのメソッドがないため、AttributeErrorになります。
正しくは、
repository = HelpRepositoryImpl(db)
use_case = HelpUseCase(repository)
とします。
Repository Interfaceは何のため?
Pythonでは、具象クラスにメソッドが存在すれば、Interfaceに書かれていなくても技術的には呼び出せます。
それでもInterfaceを用意するのは、
UseCaseがRepositoryに何を要求しているか
という契約を明確にするためです。
class HelpRepository(ABC):
@abstractmethod
async def get_help_by_slug(
self,
slug: str,
):
...
これにより、UseCaseが具体的なSQLAlchemy実装ではなく、Repositoryという抽象へ依存できます。
まとめ
役割は次のように分けると理解しやすくなります。
Route
→ HTTPを扱う
UseCase
→ アプリケーションの処理を表現する
Repository
→ DB操作を抽象化する
RepositoryImpl
→ SQLAlchemyで実際にDBへアクセスする
AsyncSession
→ RepositoryImplがDBアクセスに使う
特に重要なのは、SessionとRepositoryは別物ということです。
この境界が分かると、FastAPIのレイヤー構成やDIのコードがかなり読みやすくなります。