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 الأساسي.
التحدي
سهللنَبْنِ خط أنابيب لمعالجة الطلبات باستخدام نمط Middleware as Decorator! ستنشئ نظامًا تلتف فيه دوال middleware حول المعالجات لإضافة وظائف مثل التوقيت والتحقق والتنسيق، وكل ذلك من دون تعديل منطق المعالج الأساسي.
ستنظّم شيفرتك عبر ثلاثة ملفات:
handler.go: عرّف نوع المعالج والمعالج الأساسي الذي يعالج الطلبات.أنشئ نوعًا باسم
Handlerبوصفه دالة تأخذ سلسلة طلب وتعيد سلسلة استجابة:type Handler func(request string) stringImplement دالة
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")
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP في Go
الملفات الخارجيةمساحة العمل والـ Modules في Goالـ Packages والـ Importsالأسماء المصدرة مقابل غير المصدرةمقدمة إلى الـ OOP في Goالـ Structs كـ Classesتعريف الـ Methods في الـ Structsالـ Pointer Receivers مقابل الـ Value Receiversتهيئة الـ Structدوال الـ Constructorمراجعة - آلة حاسبة بسيطة4الواجهات
مقدمة في الواجهاتالتنفيذ الضمنيالواجهة كعقدالواجهة الفارغة (any)تأكيد النوع (Type Assertion)تبديل النوع (Type Switch)تركيب الواجهاتواجهات Stringer و Errorمراجعة - حاسبة الأشكال7التغليف
الحقول المصدرة وغير المصدرةالتغليف على مستوى الحزمةدوال الـ Getter والـ Setterإخفاء المعلومات في Goملخص - سجلات الطلاب10الأنواع العامة (Go 1.18+)
مقدمة في الأنواع العامةمعاملات الأنواعقيود الأنواعالـ Structs العامةحل بديل للـ Methods العامةملخص - المجموعات العامة13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Abstract Factoryنمط Observerنمط Strategy2تعمق في الأنواع و Structs
الأنواع الأساسية والمركبةتعريفات الأنواع المخصصةStruct TagsStructs مجهولةStructs متداخلةالقيم الصفرية والافتراضيةمراجعة - دفتر العناوين5التركيب بدلاً من الوراثة
لماذا لا تدعم Go الوراثةأساسيات تضمين الـ Structترقية الـ Methodتضمين عدة Structsالتضمين مقابل التجميعحجب الـ Methods المضمنةملخص - الهيكل الهرمي للموظفين8معالجة الأخطاء و OOP
واجهة errorأنواع الأخطاء المخصصةتغليف الأخطاء (fmt.Errorf)أخطاء Sentinelerrors.Is() و errors.As()Panic و Defer و Recoverملخص - File Parser11المكتبة القياسية والبرمجة كائنية التوجه (OOP)
io.Reader و io.Writerواجهة sort.Interfaceواجهة fmt.Stringerencoding/json مع Structsواجهة http.Handlerمراجعة - نماذج REST API14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبMiddleware كنمط مزين3المؤشرات والذاكرة
أساسيات المؤشرات في Goالمؤشرات إلى الـ Structsالتمرير بالقيمة مقابل التمرير بالمرجعدالة ()newGarbage Collection في Goملخص - بناء Linked List6تعدد الأشكال في Go
تعدد الأشكال عبر InterfacesDuck Typing في Goقواعد استيفاء الـ Interfaceمجموعات متعددة الأشكالحقن التبعيةمراجعة - معالج الدفع9التزامن والبرمجة كائنية التوجه (OOP)
أساسيات الـ Goroutinesالقنوات (Channels) والاتصالالقنوات المخزنة (Buffered) مقابل غير المخزنةجملة Selectsync.Mutex و sync.RWMutexsync.WaitGroupتصميم الـ Structs الآمنة للخيوط (Thread-Safe)مراجعة - Worker Poolتدرّب بنفسك: مترجم Go عبر الإنترنت