Sur cette page, je vais expliquer la conception de l’interface Handler.

Le Handler que vous fournissez au serveur est le cœur de votre logique de gestion des tâches asynchrones. Le rôle du gestionnaire est d’accepter une tâche et de la traiter en tenant compte du contexte. En cas d’échec du traitement, il doit signaler d’éventuelles erreurs pour une nouvelle tentative ultérieure.

L’interface est définie comme suit:

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

Il s’agit d’une interface simple, décrivant de manière concise les responsabilités d’un gestionnaire.

Il existe différentes façons d’implémenter cette interface de gestionnaire.

Voici un exemple de définition de votre propre type de structure pour gérer les tâches :

type MyTaskHandler struct {
   // ... champs
}

// Implémenter la méthode ProcessTask
func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
   // ... logique de gestion de tâches
}

Vous pouvez même définir une fonction pour satisfaire l’interface, grâce à l’adaptateur de type HandlerFunc.

func myHandler(ctx context.Context, t *asynq.Task) error {
    // ... logique de gestion de tâches
}

// h satisfait l'interface Handler
h := asynq.HandlerFunc(myHandler)

Dans la plupart des cas, vous devrez vérifier le Type de la tâche d’entrée et la traiter en conséquence.

func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
   switch t.Type() {
   case "type1":
      // gérer type1
   case "type2":
      // gérer type2
   case "typeN":
      // gérer typeN
   default:
      return fmt.Errorf("type de tâche inattendu : %q", t.Type())
   }
}

Comme vous pouvez le voir, un gestionnaire peut être composé de nombreux gestionnaires différents. Chaque cas dans l’exemple ci-dessus peut être géré par un gestionnaire dédié. C’est là que le type ServeMux entre en jeu.

Remarque : Vous n’avez pas nécessairement besoin d’utiliser le type ServeMux pour implémenter un gestionnaire, mais il peut être très utile dans de nombreux cas.
En utilisant ServeMux, vous pouvez enregistrer plusieurs gestionnaires. Il associera le type de chaque tâche aux modèles enregistrés et appellera le gestionnaire correspondant au modèle le plus proche du nom du type de tâche.

mux := asynq.NewServeMux()
mux.Handle("email:welcome", welcomeEmailHandler) // Enregistrer le gestionnaire
mux.Handle("email:reminder", reminderEmailHandler)
mux.Handle("email:", defaultEmailHandler) // Gestionnaire par défaut pour les autres types de tâches commençant par "email:"

Utilisation des Middleware

Si vous devez exécuter du code avant et/ou après le traitement des demandes, vous pouvez y parvenir avec un middleware. Un middleware est une fonction qui prend un Handler en entrée et retourne un Handler.
Voici un exemple de middleware qui enregistre le début et la fin du traitement de la tâche.

func loggingMiddleware(h asynq.Handler) asynq.Handler {
    return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error {
        start := time.Now()
        log.Printf("Traitement démarré pour %q", t.Type())
        err := h.ProcessTask(ctx, t)
        if err != nil {
            return err
        }
        log.Printf("Traitement terminé pour %q : durée écoulée = %v", t.Type(), time.Since(start))
        return nil
    })
}

Maintenant, vous pouvez utiliser ce middleware pour “envelopper” votre gestionnaire.

myHandler = loggingMiddleware(myHandler)

De plus, si vous utilisez ServeMux, vous pouvez fournir un middleware de cette manière.

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

Middleware de regroupement

Si vous avez un scénario où vous voulez appliquer un middleware à un groupe de tâches, vous pouvez y parvenir en combinant plusieurs instances de ServeMux. Une restriction est que chaque groupe de tâches doit avoir le même préfixe dans leurs noms de type.

Exemple:
Si vous avez des tâches pour gérer des commandes et des tâches pour gérer des produits, et que vous voulez appliquer une logique partagée à toutes les tâches “produit” et une autre logique partagée à toutes les tâches “commande”, vous pouvez le faire de cette manière:

productHandlers := asynq.NewServeMux()
productHandlers.Use(productMiddleware) // Appliquer la logique partagée à toutes les tâches produit
productHandlers.HandleFunc("product:update", productUpdateTaskHandler)
// ... Enregistrer d'autres gestionnaires de tâches "produit"

orderHandlers := asynq.NewServeMux()
orderHandler.Use(orderMiddleware) // Appliquer la logique partagée à toutes les tâches de commande
orderHandlers.HandleFunc("order:refund", orderRefundTaskHandler)
// ... Enregistrer d'autres gestionnaires de tâches "commande"

// Gestionnaire de niveau supérieur
mux := asynq.NewServeMux()
mux.Use(someGlobalMiddleware) // Appliquer la logique partagée à toutes les tâches
mux.Handle("product:", productHandlers)
mux.Handle("order:", orderHandlers)

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