في هذه الصفحة، سأقوم بشرح تصميم واجهة Handler.

الـHandler الذي تقدمه للخادم هو جوهر منطق معالجة المهام الخاصة بك. مسؤولية الـ Handler هي قبول مهمة ومعالجتها مع مراعاة السياق. إذا فشل المعالجة، يجب أن يقوم بالإبلاغ عن أي أخطاء لإعادة محاولة المهمة لاحقًا.

تم تعريف الواجهة على النحو التالي:

type Handler interface {
    ProcessTask(context.Context, *Task) error
}

هذه واجهة بسيطة، تصف بإيجاز مسؤوليات Handler.

هناك طرق مختلفة لتنفيذ هذه الواجهة المعالجة.

فيما يلي مثال على تحديد نوع الهيكل الخاص بك للتعامل مع المهام:

type MyTaskHandler struct {
   // ... fields
}

// تنفيذ طريقة ProcessTask
func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
   // ... منطق معالجة المهمة
}

يمكنك حتى تحديد وظيفة لتحقيق الواجهة، من خلال نوع المحول HandlerFunc.

func myHandler(ctx context.Context, t *asynq.Task) error {
    // ... منطق معالجة المهمة
}

// h يحقق واجهة Handler
h := asynq.HandlerFunc(myHandler)

في معظم الحالات، قد تحتاج إلى التحقق من Type للمهمة الداخلية والتعامل معها وفقًا لذلك.

func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
   switch t.Type() {
   case "type1":
      // التعامل مع النوع1
   case "type2":
      // التعامل مع النوع2
   case "typeN":
      // التعامل مع النوعN
   default:
      return fmt.Errorf("نوع المهمة غير متوقع: %q", t.Type())
   }
}

كما ترى، يمكن أن يتكون المعالج من العديد من المعالجين المختلفين. يمكن التعامل مع كل حالة في المثال أعلاه بمعالج مخصص. هنا تأتي دور نوع ServeMux.

ملاحظة: ليس من الضروري استخدام نوع ServeMux لتنفيذ معالج، ولكن يمكن أن يكون مفيدًا في العديد من الحالات.
من خلال استخدام ServeMux، يمكنك تسجيل العديد من المعالجين. سيقوم بمطابقة نوع كل مهمة مع الأنماط المسجلة واستدعاء المعالج المقابل للنمط الأقرب إلى اسم نوع المهمة.

mux := asynq.NewServeMux()
mux.Handle("email:welcome", welcomeEmailHandler) // تسجيل المعالج
mux.Handle("email:reminder", reminderEmailHandler)
mux.Handle("email:", defaultEmailHandler) // المعالج الافتراضي لأنواع المهام الأخرى التي تبدأ بـ "email:"

استخدام الوسيط

إذا كنت بحاجة إلى تنفيذ بعض الشفرة قبل و/أو بعد معالجة الطلبات، يمكنك تحقيق ذلك من خلال الوسيط. الوسيط هو وظيفة تأخذ Handler وتعيد Handler.

فيما يلي مثال على وسيط يسجل بداية ونهاية معالجة المهام.

func loggingMiddleware(h asynq.Handler) asynq.Handler {
    return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error {
        start := time.Now()
        log.Printf("بدء المعالجة لـ %q", t.Type())
        err := h.ProcessTask(ctx, t)
        if err != nil {
            return err
        }
        log.Printf("اكتملت المعالجة لـ %q: الوقت المنقضي = %v", t.Type(), time.Since(start))
        return nil
    })
}

الآن يمكنك استخدام هذا الوسيط لـ “تغليف” معالجك.

myHandler = loggingMiddleware(myHandler)

بالإضافة إلى ذلك، إذا كنت تستخدم ServeMux، يمكنك توفير وسيط مثل هذا.

mux := NewServeMux()
mux.Use(loggingMiddleware)

تجميع الوسيط

إذا كان لديك سيناريو ترغب في تطبيق وسيط على مجموعة من المهام، يمكنك تحقيق ذلك عن طريق دمج عدة مثيلات ServeMux. نقطة قيد واحدة هي أن كل مجموعة من المهام يجب أن تحمل نفس البادئة في أسمائها.

مثال:
إذا كان لديك مهام لمعالجة الطلبات ومهام لمعالجة المنتجات، وترغب في تطبيق منطق مشترك على جميع مهام “المنتج” ومنطق مشترك آخر على جميع مهام “الطلب”، يمكنك القيام بذلك بهذه الطريقة:

productHandlers := asynq.NewServeMux()
productHandlers.Use(productMiddleware) // تطبيق منطق مشترك على جميع مهام المنتج
productHandlers.HandleFunc("product:update", productUpdateTaskHandler)
// ...التسجيلات الأخرى لمعالجي مهام "المنتج"

orderHandlers := asynq.NewServeMux()
orderHandler.Use(orderMiddleware)// تطبيق منطق مشترك على جميع مهام الطلب
orderHandlers.HandleFunc("order:refund", orderRefundTaskHandler)
// ...التسجيلات الأخرى لمعالجي مهام "الطلب"

// المعالج الرئيسي
mux := asynq.NewServeMux()
mux.Use(someGlobalMiddleware) // تطبيق منطق مشترك على جميع المهام
mux.Handle("product:", productHandlers)
mux.Handle("order:", orderHandlers)

if err := srv.Run(mux); err != nil {
    log.Fatal(err)
}