Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

Middleware كنمط مزين

جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 97 من 107.

تُعد Middleware إحدى أكثر التطبيقات العملية لنمط Decorator في Go. في تطوير الويب، تُغلّف دوال middleware معالجات 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 في السلسلة، مضيفًا سلوكه قبل استدعاء المعالج المغلَّف أو بعده. يمكنك تكديس عدة 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 أولًا. يُستخدم هذا النمط على نطاق واسع في أطر عمل الويب Go لمعالجة request ضمن processing pipelines، مما يجعل من السهل إضافة الوظائف أو Removeها دون المساس بمنطق handler الأساسي.

challenge icon

التحدي

سهل

لنَبْنِ خط أنابيب لمعالجة الطلبات باستخدام نمط Middleware as Decorator! ستنشئ نظامًا تلتف فيه دوال middleware حول المعالجات لإضافة وظائف مثل التوقيت والتحقق والتنسيق، وكل ذلك من دون تعديل منطق المعالج الأساسي.

ستنظّم شيفرتك عبر ثلاثة ملفات:

  • handler.go: عرّف نوع المعالج والمعالج الأساسي الذي يعالج الطلبات.

    أنشئ نوعًا باسم Handler بوصفه دالة تأخذ سلسلة طلب وتعيد سلسلة استجابة:

    type Handler func(request string) string

    Implement دالة BaseHandler التي تعيد Processed: [request] لأي طلب تتلقاه.

  • middleware.go: أنشئ دوال middleware التي تلتف حول المعالجات بسلوك إضافي.

    أنشئ ثلاث دوال middleware، بحيث تأخذ كل واحدة منها Handler وتعيد Handler جديدًا:

    • TimingMiddleware: تلتف حول الاستجابة بمعلومات التوقيت، وتعيد [TIMING] [response]
    • ValidationMiddleware: تضيف بادئة التحقق، وتعيد [VALID] [response]
    • UppercaseMiddleware: تحوّل الاستجابة entire إلى أحرف كبيرة

    يجب أن تستدعي كل دالة middleware المعالج الذي تلتف حوله وأن تعزّز ناتجه.

  • main.go: ابنِ خط أنابيب للمعالجة من خلال تركيب middleware.

    اقرأ عدد طبقات middleware المطلوب Apply لها. ثم اقرأ نوع middleware لكل طبقة (timing أو validation أو uppercase)، ولفّ handler الحالي بذلك middleware. طبّقها بالترتيب الذي تُقرأ به: أول middleware تتم قراءته يصبح الغلاف outermost.

    بعد Build خط الأنابيب، اقرأ سلسلة الطلب، وعالجها عبر handler composed، ثم اطبع النتيجة.

سيتم توفير المدخلات التالية:

  • السطر 1: عدد طبقات middleware
  • الأسطر التالية: نوع middleware واحد في كل سطر (timing أو validation أو uppercase)
  • السطر last: الطلب المطلوب process

على سبيل المثال، given:

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 في Output: أول middleware يتم تطبيقه (outermost) يعمل أولًا ويرى النتيجة النهائية من جميع middleware الداخلية. يعمل middleware الخاص بـ uppercase في المثال الثاني last (innermost)، ولذلك يحوّل الاستجابة الأساسية إلى أحرف كبيرة قبل أن يضيف timing وvalidation بادئتيهما!

جرّب بنفسك

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 عبر الإنترنت