ネットワークレイヤーを作っていると、必ずこんな場面に出会います。
1つのリクエストに認証トークンを付け、ログを残し、失敗時にはリトライまで必要になります。
すべてを1つの関数に詰め込むと、いつの間にかsend()が200行になります。
そんなときに使えるのがミドルウェアパターンです。
結論から言うと、Swiftでは各機能をデコレータパターンで包み、包んだものをChain of Responsibilityで順番に接続するのが最もすっきりします。両者は競合せず、相性のよい組み合わせです。
今回は、この組み合わせを使う理由と、コードでの組み立て方を実践例で解説します。
ミドルウェアパターンとは?
ミドルウェアは、リクエストとレスポンスの間に入る中間処理層です。
AlamofireのRequestInterceptorや、サーバー側のVaporのMiddlewareを使ったことがあれば、すでに知っている概念です。
基本となる考え方は1つです。
リクエスト処理の本体はそのままにし、その前後へ機能を自由に追加・削除できるようにします。
認証、ロギング、キャッシュ、リトライなどを、それぞれ独立した部品として作ります。
すると、今回のリクエストには認証だけ、次のリクエストには認証とキャッシュというように、自由に組み合わせられます。
デコレータとChain of Responsibilityの違いは?
この2つを混同する方は少なくありません。
簡単にまとめると、次のようになります。
| 区分 | デコレータ | Chain of Responsibility |
|---|---|---|
| 目的 | 既存オブジェクトに機能を追加する | 複数の処理ハンドラを並べて順番に通す |
| 関係 | 包む構造(入れ子) | つながる構造(チェーン) |
| 通過するか | 常に次へ渡す | 途中で停止することもある |
| ミドルウェアでの役割 | 各機能を実装する | 機能を接続して順序付ける |
実際に使ってみると、この2つは対立ではなく、異なるレイヤーの話だと分かりました。
デコレータは「機能をどう追加するか」、Chain of Responsibilityは「追加した機能をどの順番で実行するか」です。
そのため、組み合わせることで互いの弱点を補えます。
実践的な組み合わせ:コードで組み立てる
まず、ミドルウェアの共通インターフェースを定義します。リクエストを受け取り、次のミドルウェアへ渡す構造です。
protocol Middleware {
// requestを受け取って処理し、, nextで次の段階へ渡す
func handle(_ request: Request,
next: (Request) async throws -> Response)
async throws -> Response
}
次に、各機能をデコレータのように1つずつ作ります。以下はロギングミドルウェアです。
struct LoggingMiddleware: Middleware {
func handle(_ request: Request,
next: (Request) async throws -> Response)
async throws -> Response {
print("➡️ リクエスト: \(request.url)")
let response = try await next(request) // 次の段階へ
print("⬅️ レスポンス: \(response.status)")
return response
}
}
ここでnext(request)を呼び出す部分が、Chain of Responsibilityのつながりです。
自分の仕事(ログ出力)を済ませ、残りを次のミドルウェアへ渡します。
最後に、複数のミドルウェアを1つのチェーンにまとめます。
func buildChain(_ middlewares: [Middleware],
final: @escaping (Request) async throws -> Response)
-> (Request) async throws -> Response {
middlewares.reversed().reduce(final) { next, mw in
{ req in try await mw.handle(req, next: next) }
}
}
ポイントは、reduceで後ろから包んでいくことです。
配列を[인증, 로깅, 재시도]順に入れると、リクエストはその順に通り、レスポンスは逆順に戻ります。玉ねぎの皮をイメージすると分かりやすいでしょう。
この設計のメリットは?
実際のプロジェクトに適用して感じたメリットをまとめます。
-
機能追加が配列に1行加えるだけになります。 キャッシュが必要なら、
CachingMiddleware()を配列に入れるだけです。 -
順序を簡単に変えられます。 認証をロギングの前後どちらで実行するかは、配列の順序を変えるだけです。
-
テストが簡単です。 各ミドルウェアが独立しているため、1つずつユニットテストを追加できます。
-
本体がすっきりします。 200行の
send()が、再び10行未満に収まります。
一方で、注意点もあります。
ミドルウェアが増えすぎると、1つのリクエストがどこを通るのか追跡しにくくなります。
そのため私は、チェーンが5、6個を超えたらロギングミドルウェアを先頭に置き、通過経路を記録します。
まとめ
デコレータで機能を作り、Chain of Responsibilityで順番に並べるこの組み合わせは、慣れればネットワークレイヤーだけでなく、イベント処理やバリデーションにもそのまま応用できます。
今日の例を小さなトイプロジェクトに組み込んでみてください。自分で層を1枚ずつ剥がしてみると、概念がずっと早く身につきます。楽しいリファクタリングを!

