モバイルアプリケーションのミドルウェア — 基礎、アーキテクチャ、活用方法

著者: IT Sectr 公開日: 2026-03-09 読了時間: 8 分

ミドルウェアは、アプリケーションのメインロジックの前後でデータを処理し、横断的関心事をビジネスコードから分離する中間ソフトウェア層です。Redux (2026)によると、ミドルウェアは集中処理によりロギングと認証のコード重複を40%削減します。Reduxミドルウェアは古典的な例ですが、このパターンはKtor Client、Bloc、Express.js、Dioなどでも広く使用されています。

重要なポイント

  • ミドルウェアはデータソースとビジネスロジックの間の層で、横断的関心事を分離します。
  • Reduxミドルウェアはdispatchをインターセプトし、reducerの前後でactionを変更します。
  • Blocはロギングと分析のためにBlocObserverを介してミドルウェアを使用します。
  • Ktor ClientはLoggingおよびAuthプラグインを使用してパイプラインベースのHTTPミドルウェアを構築します。
  • Dio InterceptorはFlutterのHTTPリクエスト用ミドルウェアで、インターセプターのチェーンを持ちます。

ミドルウェアとは?

ミドルウェアは、2つのシステムコンポーネント間に位置し、データをインターセプトして処理した後、ターゲットコンポーネントに渡すソフトウェア層です。モバイル開発では、ミドルウェアは主に3つのコンテキストで使用されます:状態管理(Redux、Bloc)、HTTP通信(Ktor Client、Dio)、イベント処理(EventBus、NotificationCenter)。基本的な価値は、アプリケーションのドメインロジックから横断的関心事(ロギング、認証、分析)を分離することです。各画面に分析呼び出しを追加する代わりに、ミドルウェアが集中管理します。

Pipe and Filterアーキテクチャ

ミドルウェアはPipe and Filterパターンを実装します:各ミドルウェアコンポーネントがデータを受け取り、処理し、チェーンの次のリンクに渡します。ミドルウェアの接続順序が処理順序を決定します — 最初のミドルウェアが生データを受け取り、最後のミドルウェアがターゲットハンドラーに渡します。JetBrains (2026)によると、このアーキテクチャにより、既存コードを変更せずにミドルウェアを追加・削除でき、テストや実験的モジュールのA/Bテストが容易になります。

Interceptorとの違い

ミドルウェアは汎用パターンであり、InterceptorはHTTP用の特殊ケースです。ミドルウェアはあらゆるデータフロー(Reduxのアクション、Blocのイベント、KtorのHTTPリクエスト)で動作します。Interceptorは常にネットワーク層に結びつき、Request/Responseのみを扱います。この違いを理解することで、適切な抽象化を選択できます:ユーザーアクションのログ記録にはミドルウェア、ヘッダーの追加にはInterceptor。大規模プロジェクトでは、両方のパターンが共存することが多く、ミドルウェアが状態を管理し、InterceptorがHTTP通信を処理します。

状態管理におけるミドルウェア

Reduxミドルウェアは、reducerに到達する前に各dispatchアクションをインターセプトします。これにより、アクションのログ記録、Redux ThunkやRedux Sagaを介した非同期リクエストの実行、アクションの変更や条件付きキャンセルが可能になります。各ミドルウェアはstore(状態へのアクセス)、next(次のミドルウェアまたはreducerへの参照)、actionを受け取り、アクションを渡すか、変更するか、ブロックするかを決定します。

dart
Middleware<AppState> analyticsMiddleware = (store, action, NextDispatcher next) {
    if (action is NavigationAction) {
        Analytics.logEvent(action.screenName);
    }
    return next(action);
};

final store = Store<AppState>>(
    reducer,
    initialState,
    middleware: [analyticsMiddleware]
);

Flutter Redux用のDartでのanalyticsMiddlewareの例。ミドルウェアはすべてのNavigationActionをインターセプトし、画面名を分析システムに記録して、チェーンを続行するためにnext(action)を呼び出します。nextが呼び出されないと、アクションはreducerに到達しません — これにより条件付きナビゲーションや不要なアクションのブロックが実現できます。配列内のミドルウェアの順序が処理順序を決定します。

非同期ミドルウェア:ThunkとSaga

Redux Thunkは、アクションオブジェクトだけでなく関数もdispatchできるミドルウェアです。関数はdispatchとgetStateを受け取り、非同期操作(HTTPクライアントを介したAPIリクエスト、DBからの読み取り)を実行し、完了時に通常のアクションをdispatchできます。これはReduxアプリケーションでのネットワークリクエストの標準的なアプローチです。Redux Sagaは、より複雑なシナリオ(リクエストのキャンセル、レースコンディション、並列処理、ユーザー入力のデバウンス)にジェネレーター(yield)を使用します。Redux Saga (2026)によると、SagaのコルーチンはネストされたThunkコールバックよりもテストとデバッグが容易です。

HTTPクライアントにおけるミドルウェア

Ktor Client(JetBrains製)は、パイプラインミドルウェアに基づいてHTTP処理を構築します。リクエストの各段階(接続設定、ヘッダー送信、レスポンス読み取り)は、パイプライン内の個別のフェーズで表現されます。開発者はclient.install { }を介してプラグイン(ミドルウェア)をインストールし、処理チェーンを取得します。インストール順序によって、どのミドルウェアが最初にデータを処理するかが決まります:Logging、Auth、ContentNegotiation、Caching。

kotlin
val client = HttpClient {
    install(Logging) {
        level = LogLevel.BODY
    }
    install(Auth) {
        bearer {
            loadTokens { BearerTokens("access", "refresh") }
        }
    }
    install(ContentNegotiation) {
        json(Json { ignoreUnknownKeys = true })
    }
    install(HttpTimeout) {
        requestTimeoutMillis = 15000
    }
}

インストールされたミドルウェアプラグインを使用したKtor Clientの設定。Logging — リクエストとレスポンスのボディを記録。Auth — リフレッシュサポート付きで自動的にBearerトークンを追加。ContentNegotiation — JSONのシリアライズ/デシリアライズ。HttpTimeout — タイムアウトを設定。各プラグインは独立しており、テスト環境ではリクエストコードを変更せずにクライアント設定を置き換えてAuthを無効化できます。

FlutterのDio Interceptor

DioはFlutterで人気のHTTPクライアントで、Interceptorをミドルウェアとして使用します。Interceptorは送信前にRequestOptionsを、受信後にResponseをインターセプトし、複数のインターセプターのチェーンをサポートします。Dio InterceptorはDart/FlutterにおけるOkHttp Interceptorに類似しています。Dio (2026)によると、RetryInterceptorとLogInterceptorはFlutterプロジェクトで最もよく使用されるミドルウェアです。

Blocアーキテクチャにおけるミドルウェア

Blocには独立したコンポーネントとしての組み込みミドルウェアはありませんが、パターンはBlocObserver(アプリケーション内の各ブロックからイベントを受け取るグローバルオブザーバー)を介して実装されます。BlocObserver.onEventは各イベント処理前に呼び出され、onTransitionは各状態遷移時に、onErrorは各例外時に呼び出されます。これは分析、ロギング、クラッシュレポート、パフォーマンス監視のための本格的なミドルウェアです。

dart
class AppBlocObserver extends BlocObserver {
    @override
    void onEvent(Bloc bloc, Object? event) {
        Crashlytics.log("${bloc.runtimeType}: $event");
        super.onEvent(bloc, event);
    }

    @override
    void onTransition(Bloc bloc, Transition transition) {
        Analytics.log(transition.eventName());
        super.onTransition(bloc, transition);
    }

    @override
    void onError(Bloc bloc, Object error, StackTrace stackTrace) {
        Crashlytics.recordError(error, stackTrace);
        super.onError(bloc, error, stackTrace);
    }
}

BlocOverrides.runZoned(() {
    runApp(MyApp());
}, blocObserver: AppBlocObserver());

DartのBloc用AppBlocObserverの例。onEventは各イベントをCrashlyticsに記録し、onTransitionはイベントを分析に送信し、onErrorは例外をクラッシュレポートに書き込みます。BlocOverrides.runZonedを介した接続により、オブザーバーはコードを変更せずにすべてのブロックでグローバルになります。テストで無効にするには、空のオブザーバーを渡すか、BlocOverridesをオーバーライドしないようにします。

ミドルウェアの適用タイミングと方法

ミドルウェアは効果的です。多くのコンポーネントに影響するタスク(ロギング、認証、分析、キャッシング、パフォーマンス監視)に適しています。同じロジックがアプリケーションのさまざまな部分で繰り返される場合(各リクエストへのトークン追加、各ユーザーアクションのログ記録、各画面遷移の分析)にミドルウェアを使用します。Dio (2026)によると、ミドルウェアによる集中処理は、各コンポーネントで個別にコードを重複する場合と比較してバグを25%削減します。

  • 使いすぎない — 過剰なミドルウェアはデバッグを複雑にし、追加呼び出しによりパフォーマンスを低下させます
  • 順序が重要 — 最初のミドルウェアは元の形式でデータを受け取り、最後のミドルウェアはすべての変更後受け取ります
  • 非同期操作 — メインUIスレッドをブロックせずに、重い処理を非同期ミドルウェアにオフロードします
  • テスト容易性 — 各ミドルウェアはモック環境を介して分離してテストする必要があります
  • チェーンを文書化 — プロジェクトでどのミドルウェアがどの順序で接続されているかを明確に記述します

ミドルウェア使用時のよくある間違い

最も一般的な間違いはミドルウェアの順序の誤りで、最初のインターセプターが2番目が追加するデータを期待する場合です。2番目に多い間違いは、メインスレッドでのミドルウェアのブロッキング操作(ファイル書き込み、同期HTTP呼び出し、暗号化)です。3番目は例外処理の欠如で、ミドルウェアが例外をスローするとチェーン全体が壊れ、アクションがreducerに到達しないか、リクエストが送信されません。常にミドルウェアロジックをtry-catchでラップし、チェーンを壊さずにエラーをCrashlyticsやSentryに記録します。コードレビュー中に定期的にミドルウェアチェーンを確認し、アーキテクチャの劣化を防止します。

よくある質問

ミドルウェアとInterceptorの違いは?

InterceptorはHTTP通信用のミドルウェアの特殊ケースです。ミドルウェアはより広範なパターンで、アクション(Redux)、イベント(Bloc)、HTTP(Ktor)、あらゆるデータフローを処理できます。Interceptorは常にネットワーク層に結びつき、Request/Responseのみを扱います。

テスト環境でミドルウェアを無効にするには?

ファクトリメソッドまたはDIコンテナ(Dagger、Koin、GetIt)を使用して、devとprodで異なるミドルウェアセットを返します。Reduxでは、テストで空の配列を渡します。Ktorでは、プラグインなしのテスト用HttpClientを使用します。主な原則は、ミドルウェアをコードにハードコードしないことです。

ミドルウェアはdispatch後にactionを変更できますか?

はい — ミドルウェアはreducerまたは次のミドルウェアに渡す前にactionを変更します。例えば、Reduxミドルウェアはディスパッチャーコードを変更せずに各アクションにメタデータ(userId、timestamp、deviceId)を追加できます。原則として、元のオブジェクトを変更せず、スプレッド演算子で新しいオブジェクトを作成します。

KtorでのミドルウェアとInterceptorの違いは?

Ktorでは、これらの用語は互換性があります — Ktor Clientミドルウェアプラグインは同じ意味です。各プラグインはHttpClientPluginを実装し、client.install { }を介してインストールされます。すべてのプラグインはリクエストパイプラインに組み込まれ、処理チェーンを形成します。

Blocのミドルウェアはエラーをどのように処理しますか?

BlocObserver.onErrorをオーバーライドします — 任意のブロックでの各例外時に呼び出されるグローバルハンドラーです。これは各ブロックでのtry-catchの代替手段で、1つのミドルウェアがエラーを集中管理し、Crashlyticsに書き込み、ユーザーにスナックバーを表示します。

まとめ

  • ミドルウェアはアプリケーションコンポーネント間の横断的関心事を分離するための汎用パターンで、Interceptorより広範です。
  • Reduxミドルウェアはロギング、非同期リクエスト(Thunk)、複雑なシナリオ(Saga)のためにdispatchをインターセプトします。
  • Ktor Clientは独立したLogging、Auth、ContentNegotiationプラグインを使用してパイプライン経由でミドルウェアを実装します。
  • BlocObserverはFlutter Bloc用ミドルウェアで、onEvent、onTransition、onErrorがすべてのブロックをグローバルに処理します。
  • Dio InterceptorはFlutter用のHTTPミドルウェアで、OkHttp Interceptorと同様のチェーンを持ちます。
  • ミドルウェアの接続順序がデータ処理シーケンスを決定します — 明示的に文書化してください。
  • ミドルウェアの適切な使用はコード重複を25-40%削減し、横断的関心事のユニットテストを簡素化します。

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください