Menu
Coddy logo textTech

Middleware как Декоратор

Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 97 из 107.

Middleware — одно из наиболее практичных применений паттерна Decorator в Go. В веб-разработке функции middleware оборачивают обработчики HTTP, добавляя сквозные задачи, такие как ведение журналов, аутентификация или измерение времени выполнения, без изменения исходного обработчика.

Ключевая идея заключается в том, что middleware принимает handler и возвращает новый handler с добавленным поведением. Это в точности соответствует паттерну «Декоратор»: один и тот же интерфейс на входе и выходе, но с расширенной функциональностью:

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 оборачивает следующий обработчик в цепочке, добавляя свое поведение до или после вызова обернутого обработчика. Вы можете объединить несколько middleware в стек, создав их композицию:

func MainHandler(request string) string {
    return "Response to: " + request
}

handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// [LOG] [AUTH] Ответ на: GET /users

Порядок обёртывания определяет порядок выполнения: outermost middleware выполняет работу первым. Этот шаблон широко используется в веб-фреймворках Go для создания pipeline обработки request, что позволяет легко добавлять или удалять функциональность, не затрагивая основную логику handler.

challenge icon

Задание

Легко

Давайте создадим pipeline обработки request, используя паттерн Middleware как Decorator! Вы создадите систему, в которой функции middleware оборачивают обработчики, добавляя такие возможности, как измерение времени, валидация и форматирование — и всё это без изменения логики основного обработчика.

Вы организуете код в трёх файлах:

  • handler.go: Определите тип обработчика и базовый обработчик, который обрабатывает request.

    Создайте тип Handler как функцию, которая принимает строку request и возвращает строку response:

    type Handler func(request string) string

    Реализуйте функцию BaseHandler, которая возвращает Processed: [request] для любого полученного request.

  • middleware.go: Создайте функции middleware, которые оборачивают обработчики, добавляя дополнительное поведение.

    Создайте три функции middleware, каждая из которых принимает Handler и возвращает новый Handler:

    • TimingMiddleware: оборачивает response информацией о времени, возвращая [TIMING] [response]
    • ValidationMiddleware: добавляет префикс валидации, возвращая [VALID] [response]
    • UppercaseMiddleware: преобразует весь response в верхний регистр

    Каждый middleware должен вызывать обёрнутый обработчик и улучшать его результат.

  • main.go: Создайте pipeline обработки, комбинируя middleware.

    Прочитайте количество слоёв middleware, которые нужно применить. Затем для каждого слоя прочитайте тип middleware (timing, validation или uppercase) и оберните текущий обработчик этим middleware. Применяйте их в порядке, в котором они были прочитаны: first прочитанный middleware становится внешней обёрткой outermost.

    После создания pipeline прочитайте строку request, обработайте её с помощью составного обработчика и выведите результат.

Будут предоставлены следующие входные данные:

  • Строка 1: количество слоёв middleware
  • Следующие строки: по одному типу middleware в каждой строке (timing, validation или uppercase)
  • Последняя строка: request для обработки

Например, для следующих данных:

2
timing
validation
GET /users

Ваш вывод должен быть таким:

[TIMING] [VALID] Processed: GET /users

А для следующих данных:

3
uppercase
timing
validation
POST /data

Ваш вывод должен быть таким:

[TIMING] [VALID] PROCESSED: POST /DATA

А для следующих данных:

1
uppercase
hello world

Ваш вывод должен быть таким:

PROCESSED: HELLO WORLD

А для следующих данных:

0
simple request

Ваш вывод должен быть таким:

Processed: simple request

Обратите внимание, как порядок оборачивания middleware влияет на вывод: first применённый middleware (внешний, outermost) выполняется первым и видит конечный результат всех внутренних middleware. Middleware в верхнем регистре во втором примере выполняется last (внутренним), поэтому он переводит базовый response в верхний регистр до того, как timing и validation добавят свои префиксы!

Попробуйте сами

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Прочитать количество слоёв middleware
	nLine, _ := reader.ReadString('\n')
	n, _ := strconv.Atoi(strings.TrimSpace(nLine))

	// Прочитать типы middleware
	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: Построить конвейер, оборачивая обработчик с помощью middleware
	// Применять middleware по порядку — первый прочитанный middleware становится самой внешней обёрткой
	// Hint: Пройти по middlewareTypes в ОБРАТНОМ порядке, чтобы добиться этого
	// Последний применённый middleware будет самым внешним (выполняется первым)

	// TODO: Обработать запрос через составленный обработчик и вывести результат
	fmt.Println("TODO: implement pipeline")
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Go