Menu
Coddy logo textTech

데코레이터로서의 Middleware

Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 97번째.

미들웨어는 Go에서 데코레이터 패턴의 가장 실용적인 응용 사례 중 하나입니다. 웹 개발에서 미들웨어 함수는 원래의 핸들러를 수정하지 않고 로깅, 인증 또는 타이밍과 같은 횡단 관심사를 추가하기 위해 HTTP 핸들러를 감쌉니다.

핵심적인 통찰은 미들웨어가 핸들러를 받아 동작이 추가된 새로운 핸들러를 반환한다는 점입니다. 이는 입출력 인터페이스는 동일하면서 기능이 강화된 데코레이터(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)
    }
}

각 미들웨어는 체인의 다음 핸들러를 감싸며, 감싸진 핸들러를 호출하기 전이나 후에 자신의 동작을 추가합니다. 여러 미들웨어를 조합하여 쌓을 수 있습니다:

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

handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// [LOG] [AUTH] Response to: GET /users

래핑 순서가 실행 순서를 결정하며, 가장 바깥쪽 미들웨어가 가장 먼저 실행됩니다. 이 패턴은 Go 웹 프레임워크의 요청 처리 파이프라인에서 광범위하게 사용되며, 핵심 핸들러 로직을 수정하지 않고도 기능을 쉽게 추가하거나 제거할 수 있게 해줍니다.

challenge icon

챌린지

쉬움

데코레이터 패턴으로서의 미들웨어를 사용하여 요청 처리 파이프라인을 구축해 봅시다! 핵심 핸들러 로직을 수정하지 않고도 타이밍, 유효성 검사, 포맷팅과 같은 기능을 추가하기 위해 미들웨어 함수가 핸들러를 감싸는 시스템을 만들게 됩니다.

코드는 세 개의 파일로 구성됩니다:

  • handler.go: 핸들러 타입과 요청을 처리하는 기본 핸들러를 정의합니다.

    요청 문자열을 받아 응답 문자열을 반환하는 함수로 Handler 타입을 생성합니다:

    type Handler func(request string) string

    수신된 모든 요청에 대해 Processed: [request]를 반환하는 BaseHandler 함수를 구현합니다.

  • middleware.go: 추가적인 동작으로 핸들러를 감싸는 미들웨어 함수를 생성합니다.

    각각 Handler를 인자로 받아 새로운 Handler를 반환하는 세 개의 미들웨어 함수를 만듭니다:

    • TimingMiddleware — 응답을 타이밍 정보로 감싸서 [TIMING] [response]를 반환합니다.
    • ValidationMiddleware — 유효성 검사 접두사를 추가하여 [VALID] [response]를 반환합니다.
    • UppercaseMiddleware — 전체 응답을 대문자로 변환합니다.

    각 미들웨어는 감싸진 핸들러를 호출하고 그 결과를 향상시켜야 합니다.

  • main.go: 미들웨어를 조합하여 처리 파이프라인을 구축합니다.

    적용할 미들웨어 계층의 수를 읽습니다. 그런 다음 각 계층에 대해 미들웨어 타입(timing, validation, 또는 uppercase)을 읽고 현재 핸들러를 해당 미들웨어로 감쌉니다. 읽은 순서대로 적용하십시오. 즉, 첫 번째로 읽은 미들웨어가 가장 바깥쪽 래퍼가 됩니다.

    파이프라인을 구축한 후, 요청 문자열을 읽고 조합된 핸들러를 통해 처리한 다음 결과를 출력합니다.

다음과 같은 입력이 제공됩니다:

  • 줄 1: 미들웨어 계층의 수
  • 다음 줄들: 한 줄에 하나씩 미들웨어 타입 (timing, validation, 또는 uppercase)
  • 마지막 줄: 처리할 요청

예를 들어, 다음과 같이 주어지면:

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

미들웨어 래핑 순서가 출력에 어떤 영향을 미치는지 주목하십시오. 첫 번째로 적용된(가장 바깥쪽) 미들웨어가 먼저 실행되며 모든 내부 미들웨어의 최종 결과를 보게 됩니다. 두 번째 예시의 uppercase 미들웨어는 마지막에 실행되므로(가장 안쪽), timing과 validation이 접두사를 추가하기 전에 기본 응답을 대문자로 변환합니다!

치트 시트

Go의 미들웨어는 원래의 핸들러를 수정하지 않고 기능을 추가하기 위해 핸들러를 감싸는 데코레이터 패턴을 따릅니다.

핸들러 타입을 함수로 정의합니다:

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)
    }
}

여러 미들웨어를 조합하여 쌓을 수 있습니다:

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

handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// 출력: [LOG] [AUTH] Response to: GET /users

가장 바깥쪽의 미들웨어가 먼저 실행되어 실행 순서를 결정합니다. 각 미들웨어는 감싸진 핸들러를 호출하기 전이나 후에 동작을 추가할 수 있습니다.

직접 해보기

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: 핸들러를 미들웨어로 감싸서 파이프라인 구축하기
	// 미들웨어를 순서대로 적용 - 처음 읽은 미들웨어가 가장 바깥쪽 래퍼가 됨
	// 힌트: 이를 구현하기 위해 middlewareTypes를 역순으로 반복 처리하세요
	// 마지막에 적용된 미들웨어가 가장 바깥쪽이 됩니다 (가장 먼저 실행됨)

	// TODO: 구성된 핸들러를 통해 요청을 처리하고 결과 출력하기
	fmt.Println("TODO: implement pipeline")
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨