FastAPIの「依存性注入(DI)」って何? なぜ必要なのかをコードで理解してみる
FastAPIのドキュメントを読んでいると、必ず出てくる**「依存性注入 (Dependency Injection: DI)」**という言葉。漢字が多くて難しそうに見えますが、実は「コードをきれいに、楽に書くための便利な仕組み」なんです。
この記事では、比喩を使わず、実際のコードの「困った例」と「解決した例」を見比べることで、FastAPIの依存性注入が何をしているのかを解説します。
1. 「依存」している状態とは?(困った例)
プログラミングにおいて、ある関数がWeb APIやデータベースなどを利用するとき、「その関数はAPIやデータベースに依存している」と言います。
例えば、「データベースからユーザー情報を取得する関数」を考えてみましょう。
# 【悪い例】関数の中で必要なツールを自分で用意している
# データベースに接続するためのライブラリ(架空)
import my_database_lib
def get_user_info(user_id: int):
# 1. 関数の中で、データベースへの接続を自分で確立する
# ※ ここで具体的な接続先URLなどを知っている必要がある!
connection = my_database_lib.connect("db://localhost:5432", user="admin", password="password")
print("DBに接続しました")
try:
# 2. 接続ツールを使ってデータを取得する
user_data = connection.fetch_user(user_id)
return user_data
finally:
# 3. 終わったら自分で接続を切る(忘れると大変!)
connection.close()
print("DB接続を切断しました")
# 使うとき
# print(get_user_info(1))
このコードの問題点
一見正しく動きますが、この関数は**「データベースへの接続作業」に強く依存**してしまっています。
- テストが非常に難しい: この関数をテストしようとすると、毎回本物のデータベースに接続しようとします。「テストの時だけ偽物のデータベース(モック)を使いたい」と思っても、関数の中に接続処理が書かれているので差し替えるのが大変です。
- コードが汚くなる: 本来やりたいのは「データの取得」だけなのに、「接続の準備」と「後片付け」のコードが混ざってしまい、読みにくくなります。
2. 依存性注入 (DI) の考え方
ここで登場するのが「依存性注入」です。考え方は非常にシンプルです。
「必要なツール(今回はデータベース接続)は、関数の中で自分で作るのではなく、引数として外から渡してもらう」
これだけです。コードを修正してみましょう。
# 【良い例】必要なツールは、引数で渡してもらう
# データベース接続ツールの「型」だけインポート(具体的な接続方法は知らなくていい)
from my_database_lib import Connection
# 変更点:引数に connection を追加!
# 「接続済みのツールを誰か渡してね」という宣言
def get_user_info(user_id: int, connection: Connection):
# 1. 自分で接続しない!渡されたツールをいきなり使うだけ!
print("渡された接続ツールを使ってデータを取得します")
user_data = connection.fetch_user(user_id)
# 2. 自分で切断もしない!(渡してくれた側の責任)
return user_data
どうでしょう? 関数の中身が劇的にシンプルになりました。「接続方法」や「後片付け」を気にする必要がなくなり、「データを使って何をするか」という本来の目的に集中できるようになりました。
これが「依存性(=必要なツール)」を外から「注入(=引数で渡す)」する、ということです。
3. FastAPIでの DI の使い方 (Depends)
FastAPIは、この便利な「依存性注入」を、Web APIの仕組みの中で超簡単に使えるようにしてくれています。それが Depends(ディペンズ) です。
FastAPIにおけるDIは、**「面倒な準備と後片付けをフレームワークに丸投げする機能」**と言えます。
実際の使い方
「APIが呼ばれたらデータベース接続を準備して、APIの処理が終わったら接続を切る」という面倒な処理を、FastAPIに自動でやらせてみましょう。
ステップ1: 「準備と後片付け」の処理を定義する
まず、面倒な処理だけを行う関数を作ります。
# データベース接続の準備と後片付けを担当する関数
def get_db_connection():
# 1. 準備(FastAPIが実行)
print("【自動】DB接続を確立します...")
db = "(接続済みのDBツールだと思ってください)"
# 2. 準備したツールをAPI関数に渡す
yield db
# 3. 後片付け(FastAPIがAPI処理終了後に実行)
print("【自動】DB接続を切断します。")
# db.close() みたいな処理
ステップ2: APIの引数で Depends を使う
APIを定義する関数の引数で、Depends を使って「さっきの関数の機能を使いたい!」と宣言します。
from fastapi import FastAPI, Depends
app = FastAPI()
# APIの定義
# 引数 db に注目! Depends(get_db_connection) と書く
@app.get("/users/{user_id}")
def read_user_api(user_id: int, db: str = Depends(get_db_connection)):
# ここに来た時点で、FastAPIが自動で準備した 'db' が使える状態になっている!
print(f"APIの中: 渡されたツール「{db}」を使って検索処理をします。")
# 自分で接続したり切断したりする必要は一切ない!
return {"user_id": user_id, "name": "テスト太郎"}
何が起きているのか?
- ブラウザからAPIにアクセスが来ます。
- FastAPIは、API関数を実行する前に、
Dependsで指定されたget_db_connectionを実行し、接続ツール(db)を準備します。 - 準備したツールを、API関数の引数
dbに渡して(注入して)、API関数を実行します。 - API関数が値を返した後、FastAPIは
get_db_connectionの残りの処理(後片付け)を実行します。
まとめ
FastAPIの依存性注入(DI)とは、難解な理論ではありません。
- 「関数の中で必要なツールを自分で準備するのをやめて、引数で受け取るようにする」というシンプルな設計方針のこと。
- FastAPIの
Dependsは、そのツールの「準備」と「後片付け」をフレームワークが自動でやってくれる便利な機能。
これを使うと、APIのロジックがすっきりして書きやすくなり、テストも簡単になります。FastAPIを使う大きなメリットの一つなので、ぜひ使ってみてください。