Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Stop Property Drilling in FastAPI: Use Request-Level Globals

2025年09月21日に「Dev.to」が公開したITニュース「Stop Property Drilling in FastAPI: Use Request-Level Globals」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

FastAPIで、ユーザー情報などを扱う`context`を深い階層まで引数として渡す「プロパティドリリング」はコードを複雑にする。Pythonの`contextvars`でリクエストごとに独立したグローバルなコンテキストを作成し、必要な時に呼び出すことで、コードが大幅に簡潔になりテストも容易になる。

ITニュース解説

Webアプリケーション、特にFastAPIのような現代的なフレームワークを使って開発を進める際、しばしば「コンテキスト情報」の扱いに悩むことがある。コンテキスト情報とは、特定の処理を実行する際に必要な、ユーザーの認証情報、権限、データベースのセッション、設定値といった、そのリクエスト固有の情報を指す。これらの情報は、Webアプリケーションの様々な部分で必要とされるため、どのように管理し、必要な場所に渡すかが開発の効率とコードの品質に大きく影響する。今回の記事は、このコンテキスト情報の扱いにまつわる「プロパティドリリング」という一般的な問題と、それをPythonの機能を用いてどのようにスマートに解決するかを解説している。

プロパティドリリングとは、文字通り「プロパティ(属性や情報)をドリルで穴を開けるように、深い階層まで伝達していく」ことを指す。具体的には、Webアプリケーションにおいて、コントローラー層で受け取ったコンテキスト情報を、直接は必要としないサービス層やリポジトリ層の関数にも引数として渡し続け、最終的にその情報が本当に必要とされる最も深いポリシー層で利用するといった状況だ。例えば、ユーザーがプロジェクトを作成するAPIを考えてみよう。このリクエストを受け取るコントローラーは、ユーザーが誰であるか、データベースに接続するための情報(データベースセッション)を知っている必要がある。しかし、実際にプロジェクトを作成する処理を行うサービス層、データを保存するリポジトリ層、そしてユーザーに作成権限があるかを確認するポリシー層でも、これらのコンテキスト情報が必要となる。結果として、コントローラーからサービスへ、サービスからリポジトリへと、context という引数が延々と引き回されることになる。

この「プロパティドリリング」は、コードの冗長性を生み出す大きな要因となる。多くの関数定義で不要な引数が増え、コードを読解する際に、その引数が本当にその関数で使われているのか、それとも次の関数に渡すためだけなのかを判別する必要が生じる。これはコードの可読性を低下させ、開発者がコードを理解するのに時間がかかるようになる。さらに、テストを行う際にも問題となる。例えば、サービス層の関数を単体テストする際、本来はサービス層で直接利用しない context 引数に対しても、モックオブジェクトを用意して渡さなければならない。これが複数の層にわたって連鎖すると、テストコードの記述が非常に複雑になり、テスト自体のメンテナンスが困難になる。

このような課題を解決するためのヒントは、Ruby on Railsの開発者であるDHHが提唱した「Current」パターンにあった。これは、リクエスト処理の開始時に、そのリクエストに関連するコンテキスト情報を特定の場所にまとめて設定し、必要になった時にいつでもその場所から取り出せるようにするという考え方だ。このアイデアをPythonのFastAPIアプリケーションに適用するために選ばれたのが、「contextvars」というPython標準ライブラリの機能である。

contextvars.ContextVar は、Pythonの非同期処理において非常に重要な役割を果たす。FastAPIは非同期処理をベースとしているため、一つのPythonプロセス内で同時に複数のリクエストを処理することが可能だ。もし一般的なグローバル変数を使ってコンテキスト情報を管理しようとすると、あるリクエストの処理中に別のリクエストがそのグローバル変数を上書きしてしまい、データが混ざり合ってしまうという「競合状態(レースコンディション)」が発生する可能性がある。しかし、ContextVar を使うと、各非同期タスク(FastAPIにおける各リクエスト処理)が独立した自身のコンテキスト変数のコピーを持つことができる。これにより、異なるリクエスト間でデータが漏洩したり、上書きされたりする心配がなく、安全にリクエスト固有の情報を扱えるようになるのだ。

具体的な解決策は、context.py というファイルで Context クラスを定義し、その中にユーザーID、ロール、組織情報、フィーチャーフラグといった、リクエスト固有の様々な属性をまとめる。そして、この Context オブジェクトを _ContextAttributes クラスの ContextVar インスタンス (_context) に格納する。_ContextAttributes のインスタンスである Current をグローバルに提供することで、アプリケーションのどの部分からでも Current.context を呼び出すだけで、現在のリクエストのコンテキスト情報にアクセスできるようになる。

この Current オブジェクトへのコンテキスト設定は、FastAPIの「ミドルウェア」機能を用いて行う。ミドルウェアとは、リクエストがコントローラーに到達する前と、レスポンスがクライアントに返される後の両方で処理を挟み込むことができる仕組みだ。CurrentContextMiddleware というカスタムミドルウェアを作成し、リクエストが到着したら、まずリクエストから現在のユーザー情報を抽出し、その情報を使って Context オブジェクトを生成する。この Context オブジェクトを Current.set() メソッドで ContextVar に設定する。そして、リクエストの処理が完了したら、finally ブロック内で Current.clear() を呼び出し、コンテキストをクリアする。これにより、次のリクエストで前のリクエストのコンテキスト情報が誤って使われるのを防ぐことができる。

この新しいパターンを導入した結果、コードは劇的にシンプルになる。以前は context 引数を引きずっていたコントローラー、サービス、リポジトリの各関数から、その引数が取り除かれる。例えば、プロジェクトを作成するサービス関数 create_project は、以前は datacontext の二つの引数を取っていたが、解決後は data のみで済むようになる。実際にコンテキスト情報が必要となるポリシー層では、Current.context を呼び出すことで、必要なユーザー情報や権限を直接取得し、それに基づいて処理を実行できる。このように、情報が必要な場所で必要な時にだけ取得するという形になるため、中間層のコードが大幅に削減され、非常にすっきりとした見通しの良いコードベースが実現する。

このアプローチにより得られるメリットは大きい。記事では、サービス層とリポジトリ層のコードが40%削減されたと報告されている。これは、コードが簡潔になり、一目で何をしているのか理解しやすくなったことを意味する。また、テストのセットアップが半分に減ったという点も重要だ。不要なモックオブジェクトの連鎖が解消され、各コンポーネントのテストが独立しやすくなったことで、テストの記述とメンテナンスが格段に楽になる。全体として、このパターンはコードの保守性を高め、開発の効率を向上させる。一見するとグローバル変数を使うことに抵抗を感じるかもしれないが、contextvars の特性を理解すれば、これは単なるグローバル変数ではなく、非同期環境下で安全にリクエスト固有の情報を管理するための強力な仕組みであることがわかるだろう。この手法は、Webアプリケーション開発において、よりクリーンで保守性の高いコードを書くための非常に有効な選択肢となる。

関連コンテンツ

関連IT用語