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