Menu
CoddyTech

Middleware dưới dạng Decorator

Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 97 trên 107.

Middleware là một trong những ứng dụng thực tiễn nhất của mẫu Decorator trong Go. Trong phát triển web, các hàm middleware bao bọc HTTP handler để bổ sung những chức năng dùng chung như ghi nhật ký, xác thực hoặc đo thời gian mà không sửa đổi handler gốc.

Điểm mấu chốt là middleware nhận một handler và trả về handler mới có thêm hành vi. Điều này tuân theo chính xác mẫu Decorator: đầu vào và đầu ra có cùng giao diện, nhưng chức năng được mở rộng:

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

Mỗi middleware bọc trình xử lý tiếp theo trong chuỗi, bổ sung hành vi trước hoặc sau khi gọi trình xử lý đó. Bạn có thể xếp chồng nhiều middleware bằng cách kết hợp chúng:

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

handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// [LOG] [AUTH] Phản hồi cho: GET /users

Thứ tự bọc quyết định thứ tự thực thi: middleware ngoài cùng chạy trước. Mẫu này được dùng rộng rãi trong các framework web Go để xử lý yêu cầu theo quy trình, giúp bạn dễ dàng thêm hoặc gỡ bỏ chức năng mà không cần sửa logic cốt lõi của handler.

challenge icon

Thử thách

Dễ

Hãy xây dựng một quy trình xử lý yêu cầu bằng mẫu Middleware as Decorator! Bạn sẽ tạo một hệ thống trong đó các hàm middleware bọc các trình xử lý để bổ sung tính năng như đo thời gian, xác thực và định dạng, mà không sửa đổi logic cốt lõi của trình xử lý.

Bạn sẽ sắp xếp code trong ba tệp:

  • handler.go: Định nghĩa kiểu trình xử lý và trình xử lý cơ sở để xử lý các yêu cầu.

    Tạo kiểu Handler dưới dạng một hàm nhận chuỗi yêu cầu và trả về chuỗi phản hồi:

    type Handler func(request string) string

    Viết hàm BaseHandler trả về Processed: [request] cho mọi yêu cầu mà hàm nhận được.

  • middleware.go: Tạo các hàm middleware để bọc trình xử lý và bổ sung hành vi.

    Tạo ba hàm middleware, mỗi hàm nhận một Handler và trả về một Handler mới:

    • TimingMiddleware: thêm thông tin đo thời gian vào phản hồi, trả về [TIMING] [response]
    • ValidationMiddleware: thêm tiền tố xác thực, trả về [VALID] [response]
    • UppercaseMiddleware: chuyển toàn bộ phản hồi thành chữ hoa

    Mỗi middleware cần gọi trình xử lý được bọc và bổ sung cho kết quả của nó.

  • main.go: Tạo quy trình xử lý bằng cách kết hợp các middleware.

    Đọc số lớp middleware cần áp dụng. Sau đó, với mỗi lớp, đọc loại middleware (timing, validation hoặc uppercase) và bọc trình xử lý hiện tại bằng middleware đó. Áp dụng theo thứ tự đã đọc: middleware được đọc đầu tiên sẽ trở thành lớp bọc ngoài cùng.

    Sau khi tạo quy trình, đọc chuỗi yêu cầu, xử lý yêu cầu bằng trình xử lý đã kết hợp và in ra kết quả.

Đầu vào sẽ có định dạng như sau:

  • Dòng 1: Số lớp middleware
  • Các dòng tiếp theo: Mỗi dòng ghi một loại middleware (timing, validation hoặc uppercase)
  • Dòng cuối: Yêu cầu cần xử lý

Ví dụ, với đầu vào:

2
timing
validation
GET /users

Đầu ra cần là:

[TIMING] [VALID] Processed: GET /users

Với đầu vào:

3
uppercase
timing
validation
POST /data

Đầu ra cần là:

[TIMING] [VALID] PROCESSED: POST /DATA

Với đầu vào:

1
uppercase
hello world

Đầu ra cần là:

PROCESSED: HELLO WORLD

Với đầu vào:

0
simple request

Đầu ra cần là:

Processed: simple request

Lưu ý thứ tự bọc middleware ảnh hưởng đến đầu ra như thế nào: middleware được áp dụng đầu tiên (ở lớp ngoài cùng) chạy trước và nhận kết quả cuối cùng từ tất cả middleware bên trong. Trong ví dụ thứ hai, middleware viết hoa chạy cuối cùng (ở lớp trong cùng), vì vậy nó chuyển phản hồi cơ sở thành chữ hoa trước khi các tiền tố đo thời gian và xác thực được thêm vào!

Tự làm thử

package main

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

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

	// Đọc số lớp middleware
	nLine, _ := reader.ReadString('\n')
	n, _ := strconv.Atoi(strings.TrimSpace(nLine))

	// Đọc các loại middleware
	middlewareTypes := make([]string, n)
	for i := 0; i < n; i++ {
		line, _ := reader.ReadString('\n')
		middlewareTypes[i] = strings.TrimSpace(line)
	}

	// Đọc yêu cầu
	request, _ := reader.ReadString('\n')
	request = strings.TrimSpace(request)

	// TODO: Bắt đầu với BaseHandler

	// TODO: Tạo pipeline bằng cách bọc handler bằng middleware
	// Áp dụng middleware theo thứ tự: middleware đọc đầu tiên sẽ là lớp bọc ngoài cùng
	// Gợi ý: Duyệt middlewareTypes theo thứ tự NGƯỢC lại để làm được điều này
	// Middleware áp dụng cuối cùng sẽ nằm ngoài cùng (chạy đầu tiên)

	// TODO: Xử lý yêu cầu qua handler đã kết hợp và in ra kết quả
	fmt.Println("TODO: implement pipeline")
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Go online