Pada halaman ini, saya akan menjelaskan desain dari antarmuka Handler.

Handler yang Anda sediakan ke server merupakan inti dari logika penanganan tugas asinkron. Tanggung jawab dari Handler adalah untuk menerima tugas dan memprosesnya sambil mempertimbangkan konteks. Jika pemrosesan gagal, ia harus melaporkan semua kesalahan untuk penjadwalan ulang tugas nanti.

Antarmuka ini didefinisikan sebagai berikut:

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

Ini adalah antarmuka sederhana yang secara ringkas menjelaskan tanggung jawab seorang Handler.

Ada berbagai cara untuk mengimplementasikan antarmuka handler ini.

Berikut adalah contoh mendefinisikan tipe struct sendiri untuk menangani tugas:

type MyTaskHandler struct {
   // ... fields
}

// Implementasikan metode ProcessTask
func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
   // ... logika penanganan tugas
}

Anda bahkan dapat mendefinisikan sebuah fungsi untuk memenuhi antarmuka, berkat tipe adaptor HandlerFunc.

func myHandler(ctx context.Context, t *asynq.Task) error {
    // ... logika penanganan tugas
}

// h memenuhi antarmuka Handler
h := asynq.HandlerFunc(myHandler)

Dalam kebanyakan kasus, Anda mungkin perlu memeriksa Type dari tugas masukan dan menanganinya secara sesuai.

func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
   switch t.Type() {
   case "type1":
      // menangani type1
   case "type2":
      // menangani type2
   case "typeN":
      // menangani typeN
   default:
      return fmt.Errorf("jenis tugas tak terduga: %q", t.Type())
   }
}

Seperti yang dapat Anda lihat, sebuah handler dapat terdiri dari banyak handler yang berbeda. Setiap kasus dalam contoh di atas dapat ditangani oleh sebuah handler yang didedikasikan. Inilah tempat di mana tipe ServeMux berperan.

Catatan: Anda tidak selalu perlu menggunakan tipe ServeMux untuk mengimplementasikan handler, tetapi ini bisa sangat berguna dalam banyak kasus.
Dengan menggunakan ServeMux, Anda dapat mendaftarkan banyak handler. Ini akan mencocokkan tipe setiap tugas dengan pola yang didaftarkan dan memanggil handler yang sesuai dengan pola yang paling mendekati nama tipe tugas.

mux := asynq.NewServeMux()
mux.Handle("email:welcome", welcomeEmailHandler) // Daftarkan handler
mux.Handle("email:reminder", reminderEmailHandler)
mux.Handle("email:", defaultEmailHandler) // Handler default untuk tipe tugas lain yang dimulai dengan "email:"

Menggunakan Middleware

Jika Anda perlu menjalankan beberapa kode sebelum dan/atau setelah menangani permintaan, Anda dapat melakukannya dengan middleware. Middleware merupakan fungsi yang mengambil Handler dan mengembalikan Handler.
Berikut adalah contoh middleware yang mencatat awal dan akhir pemrosesan tugas.

func loggingMiddleware(h asynq.Handler) asynq.Handler {
    return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error {
        start := time.Now()
        log.Printf("Pemrosesan dimulai untuk %q", t.Type())
        err := h.ProcessTask(ctx, t)
        if err != nil {
            return err
        }
        log.Printf("Pemrosesan selesai untuk %q: waktu yang dihabiskan = %v", t.Type(), time.Since(start))
        return nil
    })
}

Sekarang Anda bisa menggunakan middleware ini untuk “membungkus” handler Anda.

myHandler = loggingMiddleware(myHandler)

Selain itu, jika Anda menggunakan ServeMux, Anda dapat menyediakan middleware seperti ini.

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

Pengelompokan Middleware

Jika Anda memiliki skenario di mana Anda ingin menerapkan sebuah middleware ke sekelompok tugas, Anda dapat mencapainya dengan menggabungkan beberapa instansi ServeMux. Satu batasan adalah bahwa setiap kelompok tugas harus memiliki awalan yang sama dalam nama jenisnya.

Contoh:
Jika Anda memiliki tugas untuk menangani pesanan dan tugas untuk menangani produk, dan Anda ingin menerapkan logika bersama ke semua tugas “produk” dan logika bersama lainnya ke semua tugas “pesanan”, Anda dapat melakukannya seperti ini:

productHandlers := asynq.NewServeMux()
productHandlers.Use(productMiddleware) // Terapkan logika bersama ke semua tugas produk
productHandlers.HandleFunc("product:update", productUpdateTaskHandler)
// ... Daftarkan penangan tugas "produk" lainnya

orderHandlers := asynq.NewServeMux()
orderHandler.Use(orderMiddleware) // Terapkan logika bersama ke semua tugas pesanan
orderHandlers.HandleFunc("order:refund", orderRefundTaskHandler)
// ... Daftarkan penangan tugas "pesanan" lainnya

// Penangan tingkat atas
mux := asynq.NewServeMux()
mux.Use(someGlobalMiddleware) // Terapkan logika bersama ke semua tugas
mux.Handle("product:", productHandlers)
mux.Handle("order:", orderHandlers)

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