ミドルウェアは、アプリケーションのメインロジックの前後でデータを処理し、横断的関心事をビジネスコードから分離する中間ソフトウェア層です。Redux (2026)によると、ミドルウェアは集中処理によりロギングと認証のコード重複を40%削減します。Reduxミドルウェアは古典的な例ですが、このパターンはKtor Client、Bloc、Express.js、Dioなどでも広く使用されています。
重要なポイント
ミドルウェアは、2つのシステムコンポーネント間に位置し、データをインターセプトして処理した後、ターゲットコンポーネントに渡すソフトウェア層です。モバイル開発では、ミドルウェアは主に3つのコンテキストで使用されます:状態管理(Redux、Bloc)、HTTP通信(Ktor Client、Dio)、イベント処理(EventBus、NotificationCenter)。基本的な価値は、アプリケーションのドメインロジックから横断的関心事(ロギング、認証、分析)を分離することです。各画面に分析呼び出しを追加する代わりに、ミドルウェアが集中管理します。
ミドルウェアはPipe and Filterパターンを実装します:各ミドルウェアコンポーネントがデータを受け取り、処理し、チェーンの次のリンクに渡します。ミドルウェアの接続順序が処理順序を決定します — 最初のミドルウェアが生データを受け取り、最後のミドルウェアがターゲットハンドラーに渡します。JetBrains (2026)によると、このアーキテクチャにより、既存コードを変更せずにミドルウェアを追加・削除でき、テストや実験的モジュールのA/Bテストが容易になります。
ミドルウェアは汎用パターンであり、InterceptorはHTTP用の特殊ケースです。ミドルウェアはあらゆるデータフロー(Reduxのアクション、Blocのイベント、KtorのHTTPリクエスト)で動作します。Interceptorは常にネットワーク層に結びつき、Request/Responseのみを扱います。この違いを理解することで、適切な抽象化を選択できます:ユーザーアクションのログ記録にはミドルウェア、ヘッダーの追加にはInterceptor。大規模プロジェクトでは、両方のパターンが共存することが多く、ミドルウェアが状態を管理し、InterceptorがHTTP通信を処理します。
Reduxミドルウェアは、reducerに到達する前に各dispatchアクションをインターセプトします。これにより、アクションのログ記録、Redux ThunkやRedux Sagaを介した非同期リクエストの実行、アクションの変更や条件付きキャンセルが可能になります。各ミドルウェアはstore(状態へのアクセス)、next(次のミドルウェアまたはreducerへの参照)、actionを受け取り、アクションを渡すか、変更するか、ブロックするかを決定します。
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に到達しません — これにより条件付きナビゲーションや不要なアクションのブロックが実現できます。配列内のミドルウェアの順序が処理順序を決定します。
Redux Thunkは、アクションオブジェクトだけでなく関数もdispatchできるミドルウェアです。関数はdispatchとgetStateを受け取り、非同期操作(HTTPクライアントを介したAPIリクエスト、DBからの読み取り)を実行し、完了時に通常のアクションをdispatchできます。これはReduxアプリケーションでのネットワークリクエストの標準的なアプローチです。Redux Sagaは、より複雑なシナリオ(リクエストのキャンセル、レースコンディション、並列処理、ユーザー入力のデバウンス)にジェネレーター(yield)を使用します。Redux Saga (2026)によると、SagaのコルーチンはネストされたThunkコールバックよりもテストとデバッグが容易です。
Ktor Client(JetBrains製)は、パイプラインミドルウェアに基づいてHTTP処理を構築します。リクエストの各段階(接続設定、ヘッダー送信、レスポンス読み取り)は、パイプライン内の個別のフェーズで表現されます。開発者はclient.install { }を介してプラグイン(ミドルウェア)をインストールし、処理チェーンを取得します。インストール順序によって、どのミドルウェアが最初にデータを処理するかが決まります:Logging、Auth、ContentNegotiation、Caching。
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を無効化できます。
DioはFlutterで人気のHTTPクライアントで、Interceptorをミドルウェアとして使用します。Interceptorは送信前にRequestOptionsを、受信後にResponseをインターセプトし、複数のインターセプターのチェーンをサポートします。Dio InterceptorはDart/FlutterにおけるOkHttp Interceptorに類似しています。Dio (2026)によると、RetryInterceptorとLogInterceptorはFlutterプロジェクトで最もよく使用されるミドルウェアです。
Blocには独立したコンポーネントとしての組み込みミドルウェアはありませんが、パターンはBlocObserver(アプリケーション内の各ブロックからイベントを受け取るグローバルオブザーバー)を介して実装されます。BlocObserver.onEventは各イベント処理前に呼び出され、onTransitionは各状態遷移時に、onErrorは各例外時に呼び出されます。これは分析、ロギング、クラッシュレポート、パフォーマンス監視のための本格的なミドルウェアです。
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%削減します。
最も一般的な間違いはミドルウェアの順序の誤りで、最初のインターセプターが2番目が追加するデータを期待する場合です。2番目に多い間違いは、メインスレッドでのミドルウェアのブロッキング操作(ファイル書き込み、同期HTTP呼び出し、暗号化)です。3番目は例外処理の欠如で、ミドルウェアが例外をスローするとチェーン全体が壊れ、アクションがreducerに到達しないか、リクエストが送信されません。常にミドルウェアロジックをtry-catchでラップし、チェーンを壊さずにエラーをCrashlyticsやSentryに記録します。コードレビュー中に定期的にミドルウェアチェーンを確認し、アーキテクチャの劣化を防止します。
よくある質問
InterceptorはHTTP通信用のミドルウェアの特殊ケースです。ミドルウェアはより広範なパターンで、アクション(Redux)、イベント(Bloc)、HTTP(Ktor)、あらゆるデータフローを処理できます。Interceptorは常にネットワーク層に結びつき、Request/Responseのみを扱います。
ファクトリメソッドまたはDIコンテナ(Dagger、Koin、GetIt)を使用して、devとprodで異なるミドルウェアセットを返します。Reduxでは、テストで空の配列を渡します。Ktorでは、プラグインなしのテスト用HttpClientを使用します。主な原則は、ミドルウェアをコードにハードコードしないことです。
はい — ミドルウェアはreducerまたは次のミドルウェアに渡す前にactionを変更します。例えば、Reduxミドルウェアはディスパッチャーコードを変更せずに各アクションにメタデータ(userId、timestamp、deviceId)を追加できます。原則として、元のオブジェクトを変更せず、スプレッド演算子で新しいオブジェクトを作成します。
Ktorでは、これらの用語は互換性があります — Ktor Clientミドルウェアとプラグインは同じ意味です。各プラグインはHttpClientPluginを実装し、client.install { }を介してインストールされます。すべてのプラグインはリクエストパイプラインに組み込まれ、処理チェーンを形成します。
BlocObserver.onErrorをオーバーライドします — 任意のブロックでの各例外時に呼び出されるグローバルハンドラーです。これは各ブロックでのtry-catchの代替手段で、1つのミドルウェアがエラーを集中管理し、Crashlyticsに書き込み、ユーザーにスナックバーを表示します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。