Actix Web Data vs. State Extractors: アプリケーション状態へのデュアルアプローチ
Daniel Hayes
Full-Stack Engineer · Leapcell

はじめに
Rustで堅牢かつスケーラブルなWebアプリケーションを構築するには、アプリケーションの状態を慎重に管理することがしばしば必要となります。この状態は、データベース接続や設定値から、ユーザセッションやキャッシングレイヤまで多岐にわたります。Actix Webフレームワークでは、ハンドラ内でこれらの状態を注入してアクセスするための主要なメカニズムが2つあります。それが「Data」と「State」エクストラクタです。どちらも状態管理の目的を果たしますが、それぞれ異なるユースケースのために設計されており、異なる利点を提供します。その詳細を理解することは、効率的で保守しやすく、慣用的なActix Webアプリケーションを作成するために不可欠です。この記事では、「Data」と「State」の詳細について、それらの原則、実装、および理想的な適用シナリオを対比させながら掘り下げていきます。
コアコンセプト
エクストラクタ自体に深く入る前に、Actix Webの状態管理で頻繁に遭遇するいくつかの基本的な概念について明確に理解しておきましょう。
- アプリケーション状態 (Application State): Webアプリケーションのライフサイクル全体で、複数のリクエスト間で共有されるか、グローバルに利用可能である必要があるデータまたはリソースを指します。例としては、データベースプール、Redisクライアント、設定構造体、またはカスタムサービスインスタンスなどがあります。
 - リクエストスコープ状態 (Request-Scoped State): 単一の着信HTTPリクエストとその処理に関連するデータまたはリソースを指します。これには、ヘッダーから解析されたユーザ認証情報、リクエスト固有のトレースID、またはリクエスト処理中に生成された一時データなどが含まれる場合があります。
 - エクストラクタ (Extractor): Actix Webでは、エクストラクタは着信HTTPリクエストから特定のデータを簡単に取得するためのメカニズムです。このデータは、URIパラメータやクエリ文字列から、JSONリクエストボディ、そして後述するアプリケーション状態まで、何でも構いません。エクストラクタはハンドラシグネチャを簡素化し、型安全性を強制します。
 - Arc (Atomic Reference Counted): Rustのスマートポインタで、値の複数の所有者を許可します。値を管理している最後のArcがドロップされるときに、値もドロップされます。Arcは、非同期Webサーバーで一般的な、複数のスレッド間でデータを不変に共有するために重要です。
 - Mutex (Mutual Exclusion): 複数のスレッドからの同時アクセスから共有データを保護するために使用される同期プリミティブです。一度に1つのスレッドのみが保護されたデータにアクセスできることを保証し、データ破損を防ぎます。
Arc<Mutex<T>>は、スレッド間で可変状態を共有するための一般的なパターンです。 
Data Extractor: アプリケーション全体で共有される状態
The Data extractor in Actix Web is designed for injecting application-wide, immutable or safely mutable shared state into your handlers. When you register state with App::app_data, Actix Web wraps it in an Arc. This Arc is then available to all services (routes) within that application instance.
原則
Dataの背後にある基本原則は、アプリケーションが本質的で長期間存続するリソースを効率的に共有する方法を提供することです。状態をArcでラップすることにより、Actix Webは、スレッド安全性を損なうことなく、状態が複数のワーカー・スレッドから安全にアクセスできることを保証します。状態が可変である必要がある場合は、通常、ArcとMutexやRwLockのような内部可変性プリミティブを組み合わせます。
実装
アプリケーション全体でデータベース接続プールを共有したい場合の例で示しましょう。
use actix_web::{web, App, HttpResponse, HttpServer}; use sqlx::{PgPool, postgres::PgPoolOptions}; use std::sync::Arc; // 私たちの設定を保持するためのシンプルな構造体を定義します #[derive(Clone)] struct AppConfig { connection_string: String, // その他の設定フィールド } async fn index(pool: web::Data<PgPool>, config: web::Data<AppConfig>) -> HttpResponse { // データベースプールにアクセスします let _result = sqlx::query!("SELECT 1").fetch_one(pool.as_ref()).await; // 設定にアクセスします println!("DB connection string: {}", config.connection_string); HttpResponse::Ok().body("Hello from Actix Web!") } #[actix_web::main] async fn main() -> std::io::Result<() T> { let database_url = "postgres://user:password@localhost/database"; let pool = PgPoolOptions::new() .max_connections(5) .connect(&database_url) .await .expect("Failed to create PgPool."); let app_config = AppConfig { connection_string: database_url.to_string(), }; HttpServer::new(move || { App::new() .app_data(web::Data::new(pool.clone())) // PgPoolを登録します .app_data(web::Data::new(app_config.clone())) // AppConfigを登録します .route("/", web::get().to(index)) }) .bind(("127.0.0.1", 8080))?; .run() .await }
この例では:
PgPoolとAppConfigを一度初期化します。App::app_data(web::Data::new(my_data))を使用して、これらのリソースをアプリケーションに登録します。PgPoolのような大きな型の場合、app_dataは状態がCloneであることを期待するため(異なるワーカー・スレッドに移動されることが多いため)、.clone()が必要になることに注意してください。web::Dataは効果的にそれをArcでラップします。indexハンドラでは、web::Data<PgPool>とweb::Data<AppConfig>を引数として簡単に追加でき、Actix Webは自動的に正しいインスタンスを抽出し、注入します。
ユースケース
- データベース接続プール (
PgPool,SqlitePool,RedisPool) - 設定値 (
AppConfig,EnvConfig) - 共有サービスクライアント (例: S3クライアント、決済ゲートウェイクライアント)
 - グローバルキャッシュ (例: 
Arc<RwLock<HashMap<K, V>>>) 
State Extractor: リクエストスコープのコンテキスト状態
The State extractor (previously web::ReqData in older Actix Web versions, now typically referred to more generically or through custom extractors) is designed for injecting data that is specific to the current request and might be added or modified during the request lifecycle by middleware or earlier request processors. Unlike Data, which is configured once for the entire application, State allows you to attach data dynamically per-request.
原則
Stateの原則は、アプリケーションパイプラインを流れるリクエストのコンテキストを豊かにすることです。ミドルウェアやカスタム事前処理ステップは、その特定のリクエストに関連する情報を挿入でき、それは後続のハンドラや他のミドルウェアから便利にアクセスできます。このデータは通常、リクエスト処理が完了すると破棄されます。
実装
Actix Webのweb::Dataはアプリケーション全体の状態について明示的ですが、リクエストスコープの状態は、HttpRequestオブジェクトの拡張機能を使用するか、HttpRequestを操作するカスタムエクストラクタを定義することによって、しばしば管理されます。リクエストスコープの状態を追加する一般的なパターンには、ミドルウェアが含まれます。
ユーザIDをトークンから解析し、このUserIdを下流のハンドラで利用可能にしたい認証ミドルウェアがあると想像してみましょう。
use actix_web::{dev::{ServiceRequest, ServiceResponse, Transform}, web, App, Error, HttpRequest, HttpResponse, HttpServer}; use futures::future::{ok, Ready, LocalBoxFuture}; use std::{rc::Rc, future::{ready, Future}}; // リクエスト拡張機能に保存するシンプルなUserId構造体 #[derive(Debug, Clone)] struct UserId(u32); // 私たちのカスタムミドルウェア pub struct AuthMiddleware; impl<S, B> Transform<S, ServiceRequest> for AuthMiddleware where S: actix_web::Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static, S::Future: 'static, B: 'static, { type Response = ServiceResponse<B>; type Error = Error; type InitError = (); type Transform = AuthMiddlewareService<S>; type Future = Ready<Result<Self::Transform, Self::InitError>>; fn new_transform(&self, service: S) -> Self::Future { ok(AuthMiddlewareService { service: Rc::new(service) }) } } pub struct AuthMiddlewareService<S> { service: Rc<S>, } impl<S, B> actix_web::Service<ServiceRequest> for AuthMiddlewareService<S> where S: actix_web::Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static, S::Future: 'static, B: 'static, { type Response = ServiceResponse<B>; type Error = Error; type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>; fn poll_ready(&self, cx: &mut core::task::Context<'_>) -> core::task::Poll<Result<(), Self::Error>> { self.service.poll_ready(cx) } fn call(&self, req: ServiceRequest) -> Self::Future { let svc = self.service.clone(); Box::pin(async move { // 認証とユーザIDの抽出をシミュレートします // 実際のアプリでは、ヘッダーの解析、トークンのチェックなどが必要になります。 let user_id = UserId(123); // デモ用にユーザIDをハードコーディング // UserIdをリクエスト拡張機能に格納します // ここでリクエストスコープの状態が追加されます req.extensions_mut().insert(user_id.clone()); let res = svc.call(req).await?; Ok(res) }) } } // web::ReqData (暗黙的に、またはカスタムエクストラクタ) を使用してUserIdを抽出するハンドラ async fn user_profile(user_id: web::ReqData<UserId>) -> HttpResponse { println!("Accessed by User ID: {:?}", user_id.0); HttpResponse::Ok().body(format!("User Profile for ID: {}", user_id.0)) } #[actix_web::main] async fn main() -> std::io::Result<() T> { HttpServer::new(|| { App::new() .wrap(AuthMiddleware) // 認証ミドルウェアを適用します .route("/profile", web::get().to(user_profile)) }) .bind(("127.0.0.1", 8080))?; .run() .await }
この例では:
- 認証をシミュレートするために
AuthMiddlewareが導入されています。 - ミドルウェアの
callメソッド内で、仮の認証後、UserIdインスタンスが作成されます。 - 決定的に重要なのは、
req.extensions_mut().insert(user_id.clone());が使用され、このUserIdがリクエストのローカル拡張機能内に格納されることです。これにより、UserIdはこの特定のリクエストのライフサイクル内で利用可能になります。 user_profileハンドラは、引数としてweb::ReqData<UserId>を使用します。Actix Webは自動的にリクエストの拡張機能内でUserId型を探し、それを抽出します。
ユースケース
- 認証済みユーザの詳細 (
UserId,SessionToken) - カスタムヘッダーから解析されたリクエスト固有のパラメータ
 - リクエストトレースID (
X-Request-ID) - ミドルウェアによって生成され、下流のハンドラに渡される一時データ
 
Data vs. State: 比較
| 特徴 | web::Data<T> (アプリケーション全体の状態) | web::ReqData<T> (リクエストスコープの状態/拡張機能) | |
|---|---|---|---|
| スコープ | アプリケーションインスタンス全体 | 単一HTTPリクエスト | |
| ライフタイム | アプリケーションシャットダウンまで | リクエスト処理完了まで | |
| 作成 | App::new()セットアップ中に一度設定される | ミドルウェア/エクストラクタによってリクエストごとに動的に追加されるrives | |
| 可変性 | デフォルトで不変。安全な可変性にはArc<Mutex<T>>を使用 | ||
リクエストスコープ内では可変 (例: HttpRequest::extensions_mut()) | |||
| 基盤となる型 | Arc<T> (しばしば) | HttpRequest::extensionsに格納 (内部的にはBox<dyn Any>) | |
| アクセスパターン | App::app_data経由で注入される | ミドルウェアまたはカスタムエクストラクタでreq.extensions_mut().insert()経由で注入される | |
| 典型的な用途 | データベースプール、環境設定、共有クライアント | 認証済みユーザ、リクエストID、解析されたトークン | 
結論
web::DataとリクエストスコープのState (しばしばweb::ReqData経由でアクセスされる) の概念は、Actix Webアプリケーション内の情報を管理するために両方とも不可欠です。web::Dataは、すべてのリクエストとスレッドで一貫して共有されるリソースのための頼りになる選択肢であり、アプリケーション全体の状態を効率的に配布する方法を提供します。逆に、リクエストスコープのStateは、リクエストごとに動的に生成される情報に最適であり、認証やトレースデータなどの詳細をリクエストコンテキストにリッチさせるのに適しています。これらの2つのメカニズムを慎重に選択することにより、開発者は、グローバルな関心事とリクエスト固有のコンテキストを効果的に分離する、よく構造化されたパフォーマンスの高い保守可能なActix Webサービスを作成できます。

