In questa pagina, spiegherò il design dell’interfaccia Handler
.
L’interfaccia Handler
che fornisci al server è il cuore della tua logica di gestione dei compiti asincroni. Il compito dell’Handler è accettare un compito e elaborarlo considerando il contesto. Se il processo fallisce, dovrebbe segnalare eventuali errori per un successivo tentativo di compito.
L’interfaccia è definita come segue:
type Handler interface {
ProcessTask(context.Context, *Task) error
}
Si tratta di un’interfaccia semplice che descrive in modo succinto le responsabilità di un Handler.
Ci sono vari modi per implementare questa interfaccia di gestore.
Ecco un esempio di definizione del proprio tipo di struttura per gestire i compiti:
type MyTaskHandler struct {
// ... campi
}
// Implementare il metodo ProcessTask
func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
// ... logica di gestione del compito
}
È anche possibile definire una funzione per soddisfare l’interfaccia, grazie al tipo di adattatore HandlerFunc
.
func myHandler(ctx context.Context, t *asynq.Task) error {
// ... logica di gestione del compito
}
// h soddisfa l'interfaccia Handler
h := asynq.HandlerFunc(myHandler)
Nella maggior parte dei casi, potrebbe essere necessario controllare il campo Type
del compito in ingresso e gestirlo di conseguenza.
func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
switch t.Type() {
case "type1":
// gestire type1
case "type2":
// gestire type2
case "typeN":
// gestire typeN
default:
return fmt.Errorf("tipo di compito inaspettato: %q", t.Type())
}
}
Come si può vedere, un gestore può essere composto da molti gestori diversi. Ogni caso nell’esempio precedente può essere gestito da un gestore dedicato. È qui che entra in gioco il tipo ServeMux
.
Nota: non è necessario utilizzare necessariamente il tipo ServeMux
per implementare un gestore, ma può essere molto utile in molti casi.
Utilizzando ServeMux
, è possibile registrare più gestori. Questo corrisponderà il tipo di ogni compito con i modelli registrati e chiamerà il gestore corrispondente al modello più vicino al nome del tipo di compito.
mux := asynq.NewServeMux()
mux.Handle("email:welcome", welcomeEmailHandler) // Registro del gestore
mux.Handle("email:reminder", reminderEmailHandler)
mux.Handle("email:", defaultEmailHandler) // Gestore predefinito per altri tipi di compiti che iniziano con "email:"
Uso dei middleware
Se è necessario eseguire del codice prima e/o dopo l’elaborazione delle richieste, è possibile farlo con i middleware. Il middleware è una funzione che prende un Handler
e restituisce un Handler
.
Di seguito è riportato un esempio di middleware che registra l’inizio e la fine dell’elaborazione del compito.
func loggingMiddleware(h asynq.Handler) asynq.Handler {
return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error {
start := time.Now()
log.Printf("Elaborazione iniziata per %q", t.Type())
err := h.ProcessTask(ctx, t)
if err != nil {
return err
}
log.Printf("Elaborazione completata per %q: tempo trascorso = %v", t.Type(), time.Since(start))
return nil
})
}
Ora è possibile utilizzare questo middleware per “incapsulare” il proprio gestore.
myHandler = loggingMiddleware(myHandler)
Inoltre, se si utilizza ServeMux
, è possibile fornire i middleware in questo modo.
mux := NewServeMux()
mux.Use(loggingMiddleware)
Middleware di raggruppamento
Se hai uno scenario in cui desideri applicare un middleware a un gruppo di attività, puoi farlo combinando più istanze di ServeMux
. Un vincolo è che ogni gruppo di attività deve avere lo stesso prefisso nei loro nomi.
Esempio:
Se hai attività per gestire gli ordini e attività per gestire i prodotti, e desideri applicare la logica condivisa a tutte le attività “prodotto” e un’altra logica condivisa a tutte le attività “ordine”, puoi farlo in questo modo:
productHandlers := asynq.NewServeMux()
productHandlers.Use(productMiddleware) // Applicare la logica condivisa a tutte le attività prodotto
productHandlers.HandleFunc("product:update", productUpdateTaskHandler)
// ... Registrare altri gestori di attività "prodotto"
orderHandlers := asynq.NewServeMux()
orderHandlers.Use(orderMiddleware) // Applicare la logica condivisa a tutte le attività ordine
orderHandlers.HandleFunc("order:refund", orderRefundTaskHandler)
// ... Registrare altri gestori di attività "ordine"
// Gestore di livello superiore
mux := asynq.NewServeMux()
mux.Use(someGlobalMiddleware) // Applicare la logica condivisa a tutte le attività
mux.Handle("product:", productHandlers)
mux.Handle("order:", orderHandlers)
if err := srv.Run(mux); err != nil {
log.Fatal(err)
}