Nesta página, vou explicar o design da interface Handler.

O Handler que você fornece ao servidor é o núcleo da lógica de manipulação de tarefas assíncronas. A responsabilidade do Handler é aceitar uma tarefa e processá-la considerando o contexto. Se o processamento falhar, ele deve relatar quaisquer erros para uma tentativa posterior.

A interface é definida da seguinte forma:

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

Esta é uma interface simples, descrevendo sucintamente as responsabilidades de um Handler.

Existem várias maneiras de implementar esta interface de handler.

Aqui está um exemplo de definição do seu próprio tipo de struct para lidar com tarefas:

type MyTaskHandler struct {
   // ... campos
}

// Implemente o método ProcessTask
func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
   // ... lógica de manipulação de tarefa
}

Você até pode definir uma função para satisfazer a interface, graças ao adaptador de tipo HandlerFunc.

func myHandler(ctx context.Context, t *asynq.Task) error {
    // ... lógica de manipulação de tarefa
}

// h satisfaz a interface Handler
h := asynq.HandlerFunc(myHandler)

Na maioria dos casos, você pode precisar verificar o Type da tarefa de entrada e manipulá-la adequadamente.

func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
   switch t.Type() {
   case "type1":
      // manipular type1
   case "type2":
      // manipular type2
   case "typeN":
      // manipular typeN
   default:
      return fmt.Errorf("tipo de tarefa inesperado: %q", t.Type())
   }
}

Como você pode ver, um handler pode ser composto por muitos handlers diferentes. Cada caso no exemplo acima pode ser manipulado por um handler dedicado. Aqui é onde entra em jogo o tipo ServeMux.

Nota: Você não necessariamente precisa usar o tipo ServeMux para implementar um handler, mas pode ser muito útil em muitos casos.
Utilizando o ServeMux, você pode registrar vários handlers. Ele irá comparar o tipo de cada tarefa com os padrões registrados e chamar o handler correspondente ao padrão mais próximo do nome do tipo da tarefa.

mux := asynq.NewServeMux()
mux.Handle("email:welcome", welcomeEmailHandler) // Registrar handler
mux.Handle("email:reminder", reminderEmailHandler)
mux.Handle("email:", defaultEmailHandler) // Handler padrão para outros tipos de tarefas que começam com "email:"

Usando Middlewares

Se você precisa executar algum código antes e/ou depois de manipular solicitações, você pode alcançar isso com middlewares. Middleware é uma função que recebe um Handler e retorna um Handler.
Abaixo está um exemplo de um middleware que registra o início e o fim do processamento da tarefa.

func loggingMiddleware(h asynq.Handler) asynq.Handler {
    return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error {
        start := time.Now()
        log.Printf("Processamento iniciado para %q", t.Type())
        err := h.ProcessTask(ctx, t)
        if err != nil {
            return err
        }
        log.Printf("Processamento concluído para %q: tempo decorrido = %v", t.Type(), time.Since(start))
        return nil
    })
}

Agora você pode usar este middleware para “envolver” seu handler.

myHandler = loggingMiddleware(myHandler)

Além disso, se você estiver usando o ServeMux, você pode fornecer middlewares dessa forma.

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

Agrupamento de Middleware

Se você tiver um cenário em que deseja aplicar um middleware a um grupo de tarefas, poderá fazer isso combinando várias instâncias de ServeMux. Uma restrição é que cada grupo de tarefas precisa ter o mesmo prefixo em seus nomes de tipo.

Exemplo:
Se você tiver tarefas para lidar com pedidos e tarefas para lidar com produtos, e deseja aplicar lógica compartilhada a todas as tarefas “produto” e outra lógica compartilhada a todas as tarefas “pedido”, você pode fazer assim:

productHandlers := asynq.NewServeMux()
productHandlers.Use(productMiddleware) // Aplicar lógica compartilhada a todas as tarefas de produto
productHandlers.HandleFunc("product:update", productUpdateTaskHandler)
// ... Registro de outros manipuladores de tarefas "produto"

orderHandlers := asynq.NewServeMux()
orderHandler.Use(orderMiddleware) // Aplicar lógica compartilhada a todas as tarefas de pedido
orderHandlers.HandleFunc("order:refund", orderRefundTaskHandler)
// ... Registro de outros manipuladores de tarefas "pedido"

// Manipulador de nível superior
mux := asynq.NewServeMux()
mux.Use(someGlobalMiddleware) // Aplicar lógica compartilhada a todas as tarefas
mux.Handle("product:", productHandlers)
mux.Handle("order:", orderHandlers)

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