در این صفحه، من طراحی رابط Handler
را توضیح خواهم داد.
Handler
که به سرور ارائه میدهید، مرکز منطقی برای پردازش کارهای ناهمزمان شماست. مسئولیت Handler بر عهده دارد که یک وظیفه را پذیرفته و آن را پردازش کند و در نظر گرفتن زمینه. اگر پردازش شکست بخورد، باید هر گونه خطاها را برای تلاش بعدی وظیفه گزارش دهد.
این رابط به صورت زیر تعریف شده است:
type Handler interface {
ProcessTask(context.Context, *Task) error
}
این یک رابط ساده است، که مسئولیتهای یک Handler را به صورت کوتاه توصیف میکند.
روشهای مختلفی برای پیادهسازی این رابط handler وجود دارد.
در ادامه، یک مثال از تعریف نوع ساختار خود برای پردازش کارها آوردهشده است:
type MyTaskHandler struct {
// ... fields
}
// پیادهسازی روش 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 میتواند از بسیاری از handlerهای مختلف تشکیل شدهباشد. هر بخش در مثال بالا میتواند توسط یک handler اختصاصی پردازش شود. اینجاست که نوع ServeMux
به بازی میآید.
توجه: شما نیازی نیست که از نوع ServeMux
برای پیادهسازی یک handler استفاده کنید، اما در بسیاری از موارد میتواند بسیار مفید باشد.
با استفاده از ServeMux
، میتوانید چندین handler را ثبت کنید. آن تایپ مطابقت داده میشود با نوع هر وظیفه با الگوهای ثبتشده و handler مطابق با الگوی نزدیکترین به نام نوع وظیفه فراخوانی میشود.
mux := asynq.NewServeMux()
mux.Handle("email:welcome", welcomeEmailHandler) // ثبت handler
mux.Handle("email:reminder", reminderEmailHandler)
mux.Handle("email:", defaultEmailHandler) // handler پیشفرض برای سایر انواع وظایف که با "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
})
}
حالا میتوانید از این میانافزار برای “پوشاندن” handler خود استفاده کنید.
myHandler = loggingMiddleware(myHandler)
به علاوه، اگر از ServeMux
استفاده میکنید، میتوانید به اینگونه میانافزارها را ارائه دهید.
mux := NewServeMux()
mux.Use(loggingMiddleware)
انباشتن Middleware
اگر یک سناریو دارید که می خواهید یک middleware را به یک گروه از وظایف اعمال کنید، می توانید این کار را با ترکیب چندین نمونه 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)
}