DecoratorとしてのMiddleware
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 97/107。
ミドルウェアは、Goにおけるデコレータパターンの最も実用的な応用例の一つです。Web開発では、ミドルウェア関数がHTTPハンドラーをラップし、元のハンドラーを変更することなく、ロギング、認証、計測などの横断的関心事を追加します。
重要なポイントは、middleware が handler を受け取り、動作を追加した新しい handler を返すということです。これは Decorator パターンに完全に従っています。入力と出力で同じインターフェースを使用し、機能を拡張します。
type Handler func(request string) string
func LoggingMiddleware(next Handler) Handler {
return func(request string) string {
result := next(request)
return "[LOG] " + result
}
}
func AuthMiddleware(next Handler) Handler {
return func(request string) string {
return "[AUTH] " + next(request)
}
}各 middleware はチェーン内の next handler をラップし、ラップした handler を呼び出す前または後に、その動作を追加します。複数の middleware は、それらを合成することで積み重ねられます。
func MainHandler(request string) string {
return "Response to: " + request
}
handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// [LOG] [AUTH] への応答: GET /usersラッピングの order によって実行順序が決まります。最も外側の outermost middleware が first に実行されます。このパターンは、request 処理の pipeline のために Go の Web フレームワークで広く使用されており、コアの handler ロジックに触れることなく機能を追加または削除しやすくなります。
チャレンジ
簡単Middleware as Decorator パターンを使って、request processing pipeline を構築しましょう!middleware function が handler をラップして、timing、validation、formatting などの機能を追加するシステムを作成します。しかも、core handler logic を変更する必要はありません。
コードを3つのファイルに分けて整理します。
handler.go: handler の型と、request を処理する base handler を定義します。request string を受け取り、response string を返す function として
Handlertype を作成します。type Handler func(request string) string受け取った任意の request に対して
Processed: [request]を返すBaseHandlerfunction を Implement します。middleware.go: handler をラップして追加の動作を実行する middleware function を作成します。それぞれが
Handlerを受け取り、新しいHandlerを返す3つの middleware function を Build します。TimingMiddleware: response を timing 情報でラップし、[TIMING] [response]を返しますValidationMiddleware: validation prefix を追加し、[VALID] [response]を返しますUppercaseMiddleware: response entire を uppercase に converts します
各 middleware はラップされた handler を Call し、その結果を拡張する必要があります。
main.go: middleware を composed して processing pipeline を Build します。Apply する middleware layers の number を read します。次に各 layer について middleware type(
timing、validation、またはuppercase)を read し、現在の handler をその middleware でラップします。read した order で Apply します。first middleware read が outermost wrapper になります。pipeline を Build した後、request string を read し、composed handler を通して process し、Output を print します。
次の入力が提供されます。
- Line 1: middleware layers の number
- Following lines: 1行につき1つの middleware type(
timing、validation、またはuppercase) - Last line: process する request
たとえば、次の入力の場合:
2
timing
validation
GET /usersOutput は次のようになります。
[TIMING] [VALID] Processed: GET /usersまた、次の入力の場合:
3
uppercase
timing
validation
POST /dataOutput は次のようになります。
[TIMING] [VALID] PROCESSED: POST /DATAまた、次の入力の場合:
1
uppercase
hello worldOutput は次のようになります。
PROCESSED: HELLO WORLDまた、次の入力の場合:
0
simple requestOutput は次のようになります。
Processed: simple requestmiddleware の wrapping order が Output にどのように影響するかに注目してください。first middleware applied(outermost)は最初に実行され、すべての inner middleware による final result を受け取ります。second example の uppercase middleware は last(innermost)に実行されるため、timing と validation が prefix を adds する前に base response を uppercase にします。
自分で試してみよう
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// ミドルウェア層の数を読み取る
nLine, _ := reader.ReadString('\n')
n, _ := strconv.Atoi(strings.TrimSpace(nLine))
// ミドルウェアの種類を読み取る
middlewareTypes := make([]string, n)
for i := 0; i < n; i++ {
line, _ := reader.ReadString('\n')
middlewareTypes[i] = strings.TrimSpace(line)
}
// リクエストを読み取る
request, _ := reader.ReadString('\n')
request = strings.TrimSpace(request)
// TODO: BaseHandler から始める
// TODO: ハンドラーをミドルウェアでラップしてパイプラインを構築する
// ミドルウェアを順番に適用する - 最初に読み取ったミドルウェアが最も外側のラッパーになる
// Hint: これを実現するには middlewareTypes を逆順にループする
// 最後に適用されたミドルウェアが最も外側になる(最初に実行される)
// TODO: 構成されたハンドラーを通じてリクエストを処理し、結果を出力する
fmt.Println("TODO: implement pipeline")
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー11標準ライブラリとOOP
io.Reader と io.Writersort.Interfacefmt.Stringer インターフェースencoding/json と構造体http.Handler インターフェースまとめ:REST API モデル14デザインパターン パート2
CommandパターンAdapterパターンDecoratorパターンTemplate MethodパターンStateパターンCompositeパターンDecoratorとしてのMiddleware自分で練習してみよう: Goオンラインコンパイラ