이 페이지에서는 Handler 인터페이스의 설계를 설명하겠습니다.

서버에 제공하는 Handler는 비동기 작업 처리 로직의 핵심입니다. Handler의 책임은 작업을 수락하고 컨텍스트를 고려하여 처리하는 것입니다. 처리가 실패하면 나중에 작업을 다시 시도하기 위해 오류를 보고해야 합니다.

이 인터페이스는 다음과 같이 정의됩니다.

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

이것은 Handler의 책임을 간결하게 설명하는 간단한 인터페이스입니다.

이 Handler 인터페이스를 구현하는 다양한 방법이 있습니다.

작업을 처리하기 위해 자체 구조체 유형을 정의하는 예제는 다음과 같습니다.

type MyTaskHandler struct {
   // ... 필드
}

// ProcessTask 메서드 구현
func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
   // ... 작업 처리 로직
}

심지어 HandlerFunc 어댑터 유형 덕분에 인터페이스를 충족하는 함수를 정의할 수도 있습니다.

func myHandler(ctx context.Context, t *asynq.Task) error {
    // ... 작업 처리 로직
}

// h는 Handler 인터페이스를 충족합니다
h := asynq.HandlerFunc(myHandler)

대부분의 경우, 입력 작업의 Type을 확인하고 그에 따라 처리해야 할 수도 있습니다.

func (h *MyTaskHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
   switch t.Type() {
   case "type1":
      // type1 처리
   case "type2":
      // type2 처리
   case "typeN":
      // typeN 처리
   default:
      return fmt.Errorf("예기치 않은 작업 유형: %q", t.Type())
   }
}

Handler는 여러 다른 핸들러로 구성될 수 있습니다. 위의 예에서 각 경우는 전용 핸들러에 의해 처리될 수 있습니다. 이것이 ServeMux 유형이 필요한 이유입니다.

참고: ServeMux 유형을 사용하여 핸들러를 구현할 필요는 없지만 여러 경우에 유용할 수 있습니다.
ServeMux를 사용하면 여러 핸들러를 등록할 수 있습니다. 각 작업 유형과 등록된 패턴을 일치시키고 작업 유형 이름과 가장 가까운 패턴에 해당하는 핸들러를 호출합니다.

mux := asynq.NewServeMux()
mux.Handle("email:welcome", welcomeEmailHandler) // 핸들러 등록
mux.Handle("email:reminder", reminderEmailHandler)
mux.Handle("email:", defaultEmailHandler) // "email:"로 시작하는 다른 작업 유형에 대한 기본 핸들러

미들웨어 사용

요청을 처리하기 전/후에 일부 코드를 실행해야하는 경우 미들웨어를 사용하여 이를 달성할 수 있습니다. 미들웨어는 Handler를 가져와 Handler를 반환하는 함수입니다.
아래는 작업 처리의 시작과 끝을 로깅하는 미들웨어의 예제입니다.

func loggingMiddleware(h asynq.Handler) asynq.Handler {
    return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error {
        start := time.Now()
        log.Printf("%q에 대한 처리가 시작되었습니다", t.Type())
        err := h.ProcessTask(ctx, t)
        if err != nil {
            return err
        }
        log.Printf("%q에 대한 처리가 완료되었습니다: 경과 시간 = %v", t.Type(), time.Since(start))
        return nil
    })
}

이제 이 미들웨어를 사용하여 핸들러를 “래핑”할 수 있습니다.

myHandler = loggingMiddleware(myHandler)

또한 ServeMux를 사용하는 경우 다음과 같이 미들웨어를 제공할 수 있습니다.

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

미들웨어 그룹화

여러 작업에 미들웨어를 적용하려는 시나리오가 있는 경우, 여러 ServeMux 인스턴스를 결합하여 이를 달성할 수 있습니다. 하나의 제한 사항은 각 작업 그룹이 유형 이름의 동일한 접두사를 가져야 한다는 것입니다.

예시:
주문 처리 작업과 제품 처리 작업이 있고, “제품” 작업에 대한 모든 공유 로직을 적용하고 다른 “주문” 작업에 대한 다른 공유 로직을 적용하려는 경우, 다음과 같이 수행할 수 있습니다:

productHandlers := asynq.NewServeMux()
productHandlers.Use(productMiddleware) // 모든 제품 작업에 공유 로직 적용
productHandlers.HandleFunc("product:update", productUpdateTaskHandler)
// ... 다른 "제품" 작업 핸들러 등록

orderHandlers := asynq.NewServeMux()
orderHandler.Use(orderMiddleware) // 모든 주문 작업에 공유 로직 적용
orderHandlers.HandleFunc("order:refund", orderRefundTaskHandler)
// ... 다른 "주문" 작업 핸들러 등록

// 상위 수준 핸들러
mux := asynq.NewServeMux()
mux.Use(someGlobalMiddleware) // 모든 작업에 공유 로직 적용
mux.Handle("product:", productHandlers)
mux.Handle("order:", orderHandlers)

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