En esta página, voy a explicar el diseño de la interfaz Handler
.
El Handler
que proporcionas al servidor es el núcleo de la lógica de manejo de tareas asíncronas. La responsabilidad del Handler es aceptar una tarea y procesarla considerando el contexto. Si el procesamiento falla, debe informar cualquier error para un posterior reintento de la tarea.
La interfaz está definida de la siguiente manera:
type Handler interface {
ProcessTask(context.Context, *Task) error
}
Esta es una interfaz simple que describe de manera sucinta las responsabilidades de un Handler.
Hay varias formas de implementar esta interfaz de handler.
Aquí tienes un ejemplo de cómo definir tu propio tipo de struct para manejar tareas:
type MyTaskHandler struct {
// ... campos
}
// Implementar el método ProcessTask
func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
// ... lógica de manejo de tarea
}
Incluso puedes definir una función para satisfacer la interfaz, gracias al adaptador de tipo HandlerFunc
.
func myHandler(ctx context.Context, t *asynq.Task) error {
// ... lógica de manejo de tarea
}
// h satisface la interfaz Handler
h := asynq.HandlerFunc(myHandler)
En la mayoría de los casos, es posible que necesites verificar el Type
de la tarea de entrada y manejarla en consecuencia.
func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
switch t.Type() {
case "type1":
// manejar type1
case "type2":
// manejar type2
case "typeN":
// manejar typeN
default:
return fmt.Errorf("tipo de tarea inesperado: %q", t.Type())
}
}
Como puedes ver, un handler puede estar compuesto por muchos handlers diferentes. Cada caso en el ejemplo anterior puede ser manejado por un handler dedicado. Aquí es donde entra en juego el tipo ServeMux
.
Nota: No necesariamente necesitas usar el tipo ServeMux
para implementar un handler, pero puede ser muy útil en muchos casos.
Al usar ServeMux
, puedes registrar múltiples handlers. Coincidirá el tipo de cada tarea con los patrones registrados y llamará al handler correspondiente al patrón más cercano al nombre del tipo de tarea.
mux := asynq.NewServeMux()
mux.Handle("email:welcome", welcomeEmailHandler) // Registrar handler
mux.Handle("email:reminder", reminderEmailHandler)
mux.Handle("email:", defaultEmailHandler) // Handler predeterminado para otros tipos de tarea que comienzan con "email:"
Uso de Middleware
Si necesitas ejecutar algún código antes y/o después de manejar las solicitudes, puedes lograr esto con middleware. El middleware es una función que toma un Handler
y devuelve un Handler
.
A continuación, se muestra un ejemplo de middleware que registra el inicio y el final del procesamiento de tareas.
func loggingMiddleware(h asynq.Handler) asynq.Handler {
return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error {
start := time.Now()
log.Printf("Procesamiento iniciado para %q", t.Type())
err := h.ProcessTask(ctx, t)
if err != nil {
return err
}
log.Printf("Procesamiento completado para %q: tiempo transcurrido = %v", t.Type(), time.Since(start))
return nil
})
}
Ahora puedes usar este middleware para “envolver” tu handler.
myHandler = loggingMiddleware(myHandler)
Además, si estás utilizando ServeMux
, puedes proporcionar middleware de esta manera.
mux := NewServeMux()
mux.Use(loggingMiddleware)
Middleware de Agrupación
Si tiene un escenario en el que desea aplicar un middleware a un grupo de tareas, puede lograr esto combinando varias instancias de ServeMux
. Una restricción es que cada grupo de tareas debe tener el mismo prefijo en sus nombres de tipo.
Ejemplo:
Si tiene tareas para manejar pedidos y tareas para manejar productos, y desea aplicar lógica compartida a todas las tareas “producto” y otra lógica compartida a todas las tareas “pedido”, puede hacerlo de la siguiente manera:
manejadoresDeProducto := asynq.NewServeMux()
manejadoresDeProducto.Use(middlewareDeProducto) // Aplicar lógica compartida a todas las tareas de productos
manejadoresDeProducto.HandleFunc("producto:actualizar", manejadorTareaActualizaciónProducto)
// ... Registrar otros manejadores de tareas "producto"
manejadoresDePedido := asynq.NewServeMux()
manejadoresDePedido.Use(middlewareDePedido) // Aplicar lógica compartida a todas las tareas de pedidos
manejadoresDePedido.HandleFunc("pedido:reembolso", manejadorTareaReembolsoPedido)
// ... Registrar otros manejadores de tareas "pedido"
// Manejador de nivel superior
mux := asynq.NewServeMux()
mux.Use(algúnMiddlewareGlobal) // Aplicar lógica compartida a todas las tareas
mux.Handle("producto:", manejadoresDeProducto)
mux.Handle("pedido:", manejadoresDePedido)
if err := srv.Run(mux); err != nil {
log.Fatal(err)
}