Menu
Coddy logo textTech

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 ロジックに触れることなく機能を追加または削除しやすくなります。

challenge icon

チャレンジ

簡単

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 として Handler type を作成します。

    type Handler func(request string) string

    受け取った任意の request に対して Processed: [request] を返す BaseHandler function を 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(timingvalidation、または 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(timingvalidation、または uppercase
  • Last line: process する request

たとえば、次の入力の場合:

2
timing
validation
GET /users

Output は次のようになります。

[TIMING] [VALID] Processed: GET /users

また、次の入力の場合:

3
uppercase
timing
validation
POST /data

Output は次のようになります。

[TIMING] [VALID] PROCESSED: POST /DATA

また、次の入力の場合:

1
uppercase
hello world

Output は次のようになります。

PROCESSED: HELLO WORLD

また、次の入力の場合:

0
simple request

Output は次のようになります。

Processed: simple request

middleware の 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")
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Goオンラインコンパイラ