ในหน้านี้ ฉันจะอธิบายการออกแบบของอินเตอร์เฟซ Handler ครับ

ตัว Handler ที่คุณให้กับเซิร์ฟเวอร์คือส่วนสำคัญของตรรกะการจัดการงานแบบไม่สะดวก หน้าที่ของ Handler คือในการรับงานและประมวลผลในขณะที่พิจารณาบริบท หากการประมวลผลล้มเหลว ควรรายงานข้อผิดพลาดเพื่อลองประมวลใหม่ภายหลัง

อินเตอร์เฟซถูกนิยามไว้ดังนี้:

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

นี่คืออินเตอร์เฟซที่เรียบง่าย อธิบายหน้าที่ของ Handler โดยรวม

มีหลายวิธีในการประมวลผลอินเตอร์เฟซนี้

นี่คือตัวอย่างการกำหนดประเภทของ struct เองเพื่อจัดการงาน:

type MyTaskHandler struct {
   // ... ฟิลด์
}

// ประมวลให้ตามวิธีของเมทอด 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":
      // ประมวล type1
   case "type2":
      // ประมวล type2
   case "typeN":
      // ประมวล typeN
   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:"

การใช้ Middleware

หากคุณต้องการทำโค้ดก่อนและ/หลังจากการจัดการคำขอ, คุณสามารถทำได้ด้วย Middleware. Middleware คือฟังก์ชันที่รับ Handler และคืนค่าเป็น Handler
ด้านล่างนี้คือตัวอย่างของ Middleware ที่บันทึกเริ่มและสิ้นสุดการประมวลผลงาน

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

ตอนนี้คุณสามารถใช้ Middleware นี้เพื่อ “ห่อหุ้ม” ตัวจัดการ

myHandler = loggingMiddleware(myHandler)

อีกทางเลือกถ้าคุณกำลังใช้ ServeMux, คุณสามารถให้ Middleware ได้อย่างนี้

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

กลุ่ม Middleware

หากคุณมีสถานการณ์ที่ต้องการใช้ middleware กับกลุ่มงาน คุณสามารถดำเนินการได้ด้วยการรวม 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)
}