Auf dieser Seite erkläre ich das Design des Handler-Interfaces.

Der Handler, den Sie dem Server bereitstellen, ist der Kern Ihrer asynchronen Task-Verarbeitungslogik. Die Verantwortung des Handlers besteht darin, eine Aufgabe anzunehmen und zu verarbeiten, während er den Kontext berücksichtigt. Wenn die Verarbeitung fehlschlägt, sollte er etwaige Fehler für einen späteren erneuten Versuch der Aufgabe melden.

Das Interface ist wie folgt definiert:

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

Dies ist ein einfaches Interface, das die Verantwortlichkeiten eines Handlers prägnant beschreibt.

Es gibt verschiedene Möglichkeiten, dieses Handler-Interface zu implementieren.

Hier ist ein Beispiel, wie Sie Ihren eigenen Strukturtyp definieren können, um Aufgaben zu verarbeiten:

type MyTaskHandler struct {
   // ... Felder
}

// Implementieren der ProcessTask-Methode
func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
   // ... Aufgabenverarbeitungslogik
}

Sie können sogar eine Funktion definieren, um das Interface zu erfüllen, dank des HandlerFunc-Adaptertyps.

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

// h erfüllt das Handler-Interface
h := asynq.HandlerFunc(myHandler)

In den meisten Fällen müssen Sie möglicherweise den Eingabeaufgabentyp Type überprüfen und entsprechend verarbeiten.

func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
   switch t.Type() {
   case "type1":
      // Typ 1 verarbeiten
   case "type2":
      // Typ 2 verarbeiten
   case "typeN":
      // Typ N verarbeiten
   default:
      return fmt.Errorf("Unerwarteter Aufgabentyp: %q", t.Type())
   }
}

Wie Sie sehen können, kann ein Handler aus vielen verschiedenen Handlern zusammengesetzt werden. Jeder Fall im obigen Beispiel kann von einem speziellen Handler verarbeitet werden. Hier kommt der Typ ServeMux ins Spiel.

Hinweis: Es ist nicht unbedingt erforderlich, den Typ ServeMux zu verwenden, um einen Handler zu implementieren, aber in vielen Fällen kann dies sehr nützlich sein.
Durch die Verwendung von ServeMux können Sie mehrere Handler registrieren. Er wird den Typ jeder Aufgabe mit den registrierten Mustern abgleichen und den Handler aufrufen, der dem Muster für den Aufgabentyp am nächsten kommt.

mux := asynq.NewServeMux()
mux.Handle("email:welcome", welcomeEmailHandler) // Handler registrieren
mux.Handle("email:reminder", reminderEmailHandler)
mux.Handle("email:", defaultEmailHandler) // Standard-Handler für andere Aufgabentypen, die mit "email:" beginnen

Verwendung von Middleware

Wenn Sie vor und/oder nach der Bearbeitung von Anfragen einige Code ausführen müssen, können Sie dies mit Middleware erreichen. Middleware ist eine Funktion, die einen Handler entgegennimmt und einen Handler zurückgibt.
Im Folgenden ein Beispiel für Middleware, das den Beginn und das Ende der Aufgabenverarbeitung protokolliert.

func loggingMiddleware(h asynq.Handler) asynq.Handler {
    return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error {
        start := time.Now()
        log.Printf("Verarbeitung gestartet für %q", t.Type())
        err := h.ProcessTask(ctx, t)
        if err != nil {
            return err
        }
        log.Printf("Verarbeitung abgeschlossen für %q: vergangene Zeit = %v", t.Type(), time.Since(start))
        return nil
    })
}

Nun können Sie dieses Middleware verwenden, um Ihren Handler “zu umschließen”.

myHandler = loggingMiddleware(myHandler)

Zusätzlich können Sie, wenn Sie ServeMux verwenden, Middleware wie folgt bereitstellen.

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

Gruppierung von Middleware

Wenn Sie ein Szenario haben, in dem Sie eine Middleware auf eine Gruppe von Aufgaben anwenden möchten, können Sie dies erreichen, indem Sie mehrere ServeMux-Instanzen kombinieren. Eine Einschränkung besteht darin, dass jede Gruppe von Aufgaben denselben Präfix in ihren Typnamen haben muss.

Beispiel:
Wenn Sie Aufgaben für die Bearbeitung von Bestellungen und Aufgaben für die Bearbeitung von Produkten haben und gemeinsame Logik auf alle “Produkt” -Aufgaben und eine andere gemeinsame Logik auf alle “Bestellung” -Aufgaben anwenden möchten, können Sie dies wie folgt tun:

productHandlers := asynq.NewServeMux()
productHandlers.Use(productMiddleware) // Gemeinsame Logik auf alle Produkt-Aufgaben anwenden
productHandlers.HandleFunc("product:update", productUpdateTaskHandler)
// ... Registriere andere "Produkt" -Aufgaben-Handler

orderHandlers := asynq.NewServeMux()
orderHandlers.Use(orderMiddleware) // Gemeinsame Logik auf alle Bestell-Aufgaben anwenden
orderHandlers.HandleFunc("order:refund", orderRefundTaskHandler)
// ... Registriere andere "Bestellung" -Aufgaben-Handler

// Handler auf höchster Ebene
mux := asynq.NewServeMux()
mux.Use(someGlobalMiddleware) // Gemeinsame Logik auf alle Aufgaben anwenden
mux.Handle("product:", productHandlers)
mux.Handle("order:", orderHandlers)

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