ในหน้านี้ ฉันจะอธิบายการออกแบบของอินเตอร์เฟซ 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)
}