Na tej stronie wyjaśnię projekt interfejsu Handler.

Handler, który dostarczasz serwerowi, jest rdzeniem logiki obsługi zadań asynchronicznych. Obowiązkiem Handlera jest akceptowanie zadania i jego przetwarzanie, uwzględniając kontekst. Jeśli przetwarzanie zakończy się niepowodzeniem, powinien zgłosić wszelkie błędy dla późniejszej ponownej próby wykonania zadania.

Interfejs jest zdefiniowany następująco:

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

To proste, zwięzłe określenie obowiązków Handlera.

Istnieje wiele sposobów implementacji tego interfejsu handlera.

Oto przykład zdefiniowania własnego typu struktury do obsługi zadań:

type MyTaskHandler struct {
   // ... pola
}

// Implementacja metody ProcessTask
func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
   // ... logika obsługi zadania
}

Możesz nawet zdefiniować funkcję, aby zaspokoić interfejs, dzięki adapterowi typu HandlerFunc.

func myHandler(ctx context.Context, t *asynq.Task) error {
    // ... logika obsługi zadania
}

// h zaspokaja interfejs Handler
h := asynq.HandlerFunc(myHandler)

W większości przypadków konieczne może być sprawdzenie typu wejściowego zadania, Type, i odpowiednie jego obsłużenie.

func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
   switch t.Type() {
   case "type1":
      // obsłuż typ1
   case "type2":
      // obsłuż typ2
   case "typeN":
      // obsłuż typN
   default:
      return fmt.Errorf("nieoczekiwany typ zadania: %q", t.Type())
   }
}

Jak widać, handler może składać się z wielu różnych handlerów. Każdy przypadek w powyższym przykładzie może być obsługiwany przez dedykowanego handlera. W tym miejscu przydaje się typ ServeMux.

Uwaga: Niekoniecznie trzeba używać typu ServeMux do implementacji handlera, ale może być bardzo przydatny w wielu przypadkach.
Korzystając z ServeMux, można zarejestrować wiele handlerów. Dopasuje on typ każdego zadania do zarejestrowanych wzorców i wywoła handler odpowiadający wzorcowi najbliższemu nazwie typu zadania.

mux := asynq.NewServeMux()
mux.Handle("email:welcome", welcomeEmailHandler) // Zarejestruj handler
mux.Handle("email:reminder", reminderEmailHandler)
mux.Handle("email:", defaultEmailHandler) // Domyślny handler dla innych typów zadań zaczynających się od "email:"

Korzystanie z Middleware

Jeśli potrzebujesz wykonać jakiś kod przed i/lub po obsłudze żądań, możesz to osiągnąć za pomocą middleware. Middleware to funkcja, która przyjmuje Handler i zwraca Handler.
Poniżej znajduje się przykład middleware, który rejestruje początek i koniec przetwarzania zadania.

func loggingMiddleware(h asynq.Handler) asynq.Handler {
    return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error {
        start := time.Now()
        log.Printf("Rozpoczęcie przetwarzania %q", t.Type())
        err := h.ProcessTask(ctx, t)
        if err != nil {
            return err
        }
        log.Printf("Zakończenie przetwarzania %q: czas trwania = %v", t.Type(), time.Since(start))
        return nil
    })
}

Teraz możesz użyć tego middleware, aby “opakować” swój handler.

myHandler = loggingMiddleware(myHandler)

Dodatkowo, jeśli korzystasz z ServeMux, możesz dostarczyć middleware w ten sposób.

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

Grupowanie oprogramowania pośredniczącego

Jeśli masz scenariusz, w którym chcesz zastosować oprogramowanie pośredniczące do grupy zadań, możesz to osiągnąć, łącząc wiele instancji ServeMux. Ograniczeniem jest konieczność posiadania tego samego prefiksu w nazwach typów dla każdej grupy zadań.

Przykład:
Jeśli masz zadania do obsługi zamówień i zadania do obsługi produktów, i chcesz zastosować współdzieloną logikę do wszystkich zadań “produktu” oraz inną współdzieloną logikę do wszystkich zadań “zamówienia”, możesz to zrobić w ten sposób:

productHandlers := asynq.NewServeMux()
productHandlers.Use(productMiddleware) // Zastosuj współdzieloną logikę do wszystkich zadań produktów
productHandlers.HandleFunc("product:update", productUpdateTaskHandler)
// ... Zarejestruj inne obsługiwane "produktowe" zadania

orderHandlers := asynq.NewServeMux()
orderHandler.Use(orderMiddleware) // Zastosuj współdzieloną logikę do wszystkich zadań zamówień
orderHandlers.HandleFunc("order:refund", orderRefundTaskHandler)
// ... Zarejestruj inne obsługiwane zadania "zamówień"

// Obsługa na najwyższym poziomie
mux := asynq.NewServeMux()
mux.Use(someGlobalMiddleware) // Zastosuj współdzieloną logikę do wszystkich zadań
mux.Handle("product:", productHandlers)
mux.Handle("order:", orderHandlers)

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