در بسیاری از پروژههای Laravel لازم نیست تمام عملیات دقیقاً در همان لحظهای که کاربر درخواست خود را ارسال میکند انجام شوند. بعضی عملیات مانند ارسال ایمیل، ارسال اعلان، پردازش فایل، تولید گزارش، ارتباط با APIهای خارجی یا پردازش تعداد زیادی رکورد ممکن است زمان زیادی نیاز داشته باشند.
اگر چنین کارهایی مستقیماً داخل Request اجرا شوند، کاربر مجبور میشود تا پایان عملیات منتظر بماند و در پروژههای بزرگتر حتی ممکن است با Timeout، کاهش سرعت یا فشار بیش از حد روی سرور مواجه شویم.
Laravel برای مدیریت چنین کارهایی مفهوم Job را در اختیار توسعهدهنده قرار میدهد.
Job به ما اجازه میدهد یک عملیات مشخص را به شکل یک واحد مستقل از منطق برنامه تعریف کنیم و در صورت نیاز آن را مستقیماً اجرا کنیم یا برای اجرای غیرهمزمان در اختیار سیستم Queue قرار دهیم.
در این مقاله با مفهوم Job در Laravel، ساخت Job، Dispatch کردن، اجرای Job در پسزمینه، Retry، مدیریت خطا، Failed Jobs و چند الگوی کاربردی آن آشنا میشویم.
Job در Laravel چیست؟
در Laravel، Job یک کلاس مستقل است که یک کار مشخص را انجام میدهد.
برای مثال فرض کنید بعد از ثبت سفارش باید چند عملیات انجام شود:
- پردازش سفارش
- ارسال ایمیل
- ارسال اعلان
- محاسبه اطلاعات
- ارتباط با یک API خارجی
- تولید فاکتور
بهجای اینکه تمام این عملیات را مستقیماً داخل Controller انجام دهیم، میتوانیم هر عملیات مهم را به یک Job منتقل کنیم.
برای مثال:
ProcessOrder::dispatch($order);
در این حالت Controller فقط Job را Dispatch میکند و منطق مربوط به پردازش سفارش داخل کلاس ProcessOrder قرار میگیرد.
به زبان ساده:
> Job در Laravel یک واحد مستقل برای انجام یک کار مشخص در برنامه است.
چرا از Job در Laravel استفاده کنیم؟
استفاده از Job چند مزیت مهم دارد.
۱. جدا کردن منطق سنگین از Request
فرض کنید کاربر روی دکمه «ثبت سفارش» کلیک میکند و بعد از ثبت سفارش باید یک فایل PDF بزرگ تولید شود.
اگر تولید PDF را مستقیماً داخل Controller انجام دهیم، Request تا پایان تولید فایل منتظر میماند.
اما میتوانیم آن را به Job منتقل کنیم:
GenerateInvoice::dispatch($order);
در نتیجه ساختار برنامه تمیزتر میشود.
۲. اجرای عملیات زمانبر در پسزمینه
یکی از مهمترین کاربردهای Job اجرای کارهایی است که لازم نیست کاربر منتظر پایان آنها بماند.
مثلاً:
ثبت سفارش
↓
ذخیره سفارش
↓
Dispatch Job
↓
پاسخ سریع به کاربر
↓
پردازش Job
در چنین معماری تجربه کاربر بسیار بهتر میشود.
۳. امکان Retry کردن عملیات ناموفق
فرض کنید Job باید به یک API خارجی متصل شود اما API برای چند ثانیه در دسترس نیست.
در چنین شرایطی لازم نیست عملیات را برای همیشه شکستخورده در نظر بگیریم.
Laravel میتواند Job را دوباره اجرا کند.
۴. مدیریت بهتر خطاها
Jobها را میتوان به شکلی طراحی کرد که در صورت شکست، وضعیت آنها قابل بررسی باشد و در صورت نیاز دوباره اجرا شوند.
۵. مقیاسپذیری بهتر
در پروژههای بزرگ تعداد زیادی عملیات میتوانند به جای اجرای مستقیم، وارد Queue شوند.
سپس Workerها این عملیات را پردازش میکنند.
این معماری برای پروژههایی که حجم پردازش بالایی دارند اهمیت زیادی دارد.
# ساخت Job در Laravel
برای ساخت یک Job از Artisan استفاده میکنیم:
php artisan make:job ProcessOrder
Laravel کلاس Job را در مسیر زیر ایجاد میکند:
app/Jobs/ProcessOrder.php
یک Job ساده معمولاً ساختاری شبیه این دارد:
<?php
namespace App\Jobs;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class ProcessOrder implements ShouldQueue
{
use Queueable;
public function __construct()
{
//
}
public function handle(): void
{
//
}
}
مهمترین قسمت این کلاس متد handle() است.
# متد handle در Job چیست؟
منطق اصلی اجرای Job داخل متد handle() قرار میگیرد.
برای مثال:
public function handle(): void
{
// عملیات Job
}
اگر قرار باشد Job یک سفارش را پردازش کند، میتوانیم منطق مربوط به آن را داخل همین متد قرار دهیم.
مثلاً:
public function handle(): void
{
$this->order->update([
'status' => 'processing',
]);
}
البته در پروژه واقعی بهتر است منطق پیچیده را بیش از حد داخل Job قرار ندهیم و مسئولیتهای مختلف را به Serviceها یا کلاسهای مناسب منتقل کنیم.
Job بهتر است بیشتر نقش هماهنگکننده اجرای عملیات را داشته باشد.
# ارسال اطلاعات به Job
یکی از قابلیتهای مهم Job این است که میتوانیم اطلاعات مورد نیاز را هنگام ساخت آن ارسال کنیم.
فرض کنید مدل Order داریم.
class ProcessOrder implements ShouldQueue
{
use Queueable;
public function __construct(
public int $orderId
) {}
public function handle(): void
{
$order = Order::find($this->orderId);
if (!$order) {
return;
}
// پردازش سفارش
}
}
حالا میتوانیم Job را به شکل زیر Dispatch کنیم:
ProcessOrder::dispatch($order->id);
در این حالت مقدار شناسه سفارش در Job ذخیره میشود.
# آیا بهتر است کل Model را به Job ارسال کنیم؟
Laravel با استفاده از SerializesModels میتواند مدلهای Eloquent را نیز در Job مدیریت کند.
مثلاً:
use Illuminate\Foundation\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\SerializesModels;
class ProcessOrder implements ShouldQueue
{
use Queueable, SerializesModels;
public function __construct(
public Order $order
) {}
public function handle(): void
{
$this->order->update([
'status' => 'processing',
]);
}
}
سپس:
ProcessOrder::dispatch($order);
این روش بسیار کاربردی است.
با این حال، برای Jobهای حساس و سنگین، معمولاً بهتر است فقط اطلاعات ضروری مانند ID را ارسال کنیم.
مثلاً:
public function __construct(
public int $orderId
) {}
و در handle() مدل را دوباره از دیتابیس دریافت کنیم.
این روش کنترل بیشتری روی دادههای مورد استفاده Job ایجاد میکند.
# Dispatch در Laravel چیست؟
برای اجرای یک Job از dispatch() استفاده میکنیم.
مثلاً:
ProcessOrder::dispatch($order->id);
این یکی از رایجترین روشهای اجرای Job در Laravel است.
فرض کنید Controller داریم:
public function store(Request $request)
{
$order = Order::create([
'user_id' => auth()->id(),
'total' => $request->total,
]);
ProcessOrder::dispatch($order->id);
return response()->json([
'message' => 'سفارش ثبت شد.',
]);
}
در این حالت Controller وظیفه ثبت سفارش را انجام میدهد و پردازش بعدی را به Job واگذار میکند.
# Job به صورت همزمان یا غیرهمزمان
یک نکته مهم درباره Job این است که ساختن Job بهتنهایی به معنی اجرای پسزمینهای آن نیست.
Job میتواند به شکلهای مختلف اجرا شود.
اگر Queue روی حالت sync باشد، عملیات Job همان لحظه و داخل همان Request اجرا میشود.
برای مثال:
QUEUE_CONNECTION=sync
در این حالت:
ProcessOrder::dispatch($order->id);
عملاً منتظر اجرای Job خواهد بود.
اما اگر Queue روی یک Driver مناسب برای پردازش پسزمینه تنظیم شده باشد، Job وارد Queue میشود و Worker آن را پردازش میکند.
بنابراین:
Job
│
├── Sync
│ └── اجرای فوری
│
└── Queue
└── اجرای غیرهمزمان توسط Worker
جزئیات کامل Driverهای Queue و راهاندازی Worker را در مقاله بعدی کلاستر، یعنی Queue در Laravel بررسی خواهیم کرد.
# ShouldQueue چیست؟
اگر بخواهیم Job به صورت Queued اجرا شود، معمولاً Job را به ShouldQueue مجهز میکنیم:
use Illuminate\Contracts\Queue\ShouldQueue;
class ProcessOrder implements ShouldQueue
{
//
}
به این ترتیب Laravel میداند که این Job باید برای پردازش توسط سیستم Queue آماده باشد.
یک Job معمولی میتواند چنین ساختاری داشته باشد:
<?php
namespace App\Jobs;
use App\Models\Order;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\SerializesModels;
class ProcessOrder implements ShouldQueue
{
use Queueable, SerializesModels;
public function __construct(
public int $orderId
) {}
public function handle(): void
{
$order = Order::find($this->orderId);
if (!$order) {
return;
}
$order->update([
'status' => 'processing',
]);
}
}
# تفاوت Job، Queue و Worker چیست؟
این سه مفهوم معمولاً با یکدیگر اشتباه گرفته میشوند.
| مفهوم | وظیفه |
|---|---|
| Job | تعریف کاری که باید انجام شود |
| Queue | محل قرار گرفتن کارهای منتظر اجرا |
| Worker | پردازش و اجرای Jobهای موجود در Queue |
مثلاً تصور کنید فروشگاه شما ۱۰۰۰ سفارش دارد.
هر سفارش میتواند یک Job ایجاد کند:
Job 1
Job 2
Job 3
...
Job 1000
این Jobها وارد Queue میشوند.
سپس Worker آنها را یکییکی یا طبق تنظیمات پردازش میکند.
بنابراین:
Application
↓
Job
↓
Queue
↓
Worker
↓
Execution
# اجرای Job با Queue Worker
وقتی از Queue استفاده میکنیم، Worker وظیفه پردازش Jobها را بر عهده دارد.
دستور رایج اجرای Worker:
php artisan queue:work
Worker شروع میکند به گوش دادن به Queue و Jobهای موجود را پردازش میکند.
در محیط توسعه میتوانید آن را اجرا کنید:
php artisan queue:work
و سپس در درخواست دیگری Job را Dispatch کنید:
ProcessOrder::dispatch($order->id);
Worker Job را دریافت کرده و اجرا میکند.
# تعیین تأخیر برای اجرای Job
گاهی نمیخواهیم Job بلافاصله اجرا شود.
برای مثال میخواهیم ایمیل یادآوری سفارش ۱۰ دقیقه بعد ارسال شود.
میتوانیم از delay() استفاده کنیم:
SendOrderReminder::dispatch($order->id)
->delay(now()->addMinutes(10));
در این حالت Job برای اجرای فوری در نظر گرفته نمیشود و زمان اجرای آن به آینده منتقل میشود.
این قابلیت برای مواردی مانند موارد زیر کاربرد دارد:
- یادآوری پرداخت
- ارسال ایمیل
- ارسال پیامک
- Notification
- پردازش مرحلهای
- اجرای عملیات بعد از مدت مشخص
# Retry در Job چیست؟
گاهی یک Job به دلیل موقتی شکست میخورد.
برای مثال:
- API موقتاً قطع شده است.
- سرویس خارجی پاسخ نداده است.
- Database موقتاً در دسترس نبوده است.
- Timeout اتفاق افتاده است.
در چنین شرایطی میتوان Job را دوباره امتحان کرد.
برای تعیین تعداد تلاشها میتوانیم از $tries استفاده کنیم:
public int $tries = 3;
یعنی Job میتواند حداکثر سه بار تلاش شود.
مثلاً:
class SyncOrder implements ShouldQueue
{
use Queueable;
public int $tries = 3;
public function handle(): void
{
// عملیات
}
}
# تعیین فاصله بین Retryها با backoff
گاهی بهتر است بعد از شکست Job بلافاصله دوباره اجرا نشود.
میتوانیم برای آن Backoff تعیین کنیم:
public function backoff(): array
{
return [10, 30, 60];
}
در این مثال تلاشهای بعدی با فاصلههای مشخص انجام میشوند.
این روش مخصوصاً برای APIهای خارجی بسیار مفید است.
مثلاً اگر یک سرویس خارجی موقتاً در دسترس نباشد، بهتر است چند ثانیه صبر کنیم و دوباره درخواست بفرستیم.
# تعیین Timeout برای Job
بعضی Jobها ممکن است به دلیل مشکل در API یا عملیات پردازشی برای مدت بسیار طولانی اجرا شوند.
برای جلوگیری از اجرای بینهایت میتوان Timeout تعریف کرد.
مثلاً:
public int $timeout = 120;
یعنی Job نباید بیشتر از مقدار مشخصشده برای اجرا زمان مصرف کند.
در پروژههای واقعی، Timeout مناسب را باید بر اساس نوع عملیات تعیین کرد.
# Failed Job چیست؟
اگر Job بعد از تعداد تلاشهای مجاز همچنان با شکست مواجه شود، میتوان آن را به عنوان Failed Job مدیریت کرد.
برای مثال اگر:
public int $tries = 3;
باشد و Job سه بار شکست بخورد، دیگر نباید بدون محدودیت دوباره اجرا شود.
Laravel امکاناتی برای مدیریت Jobهای شکستخورده در اختیار ما قرار میدهد.
میتوان Jobهای شکستخورده را بررسی کرد:
php artisan queue:failed
# اجرای مجدد Failed Job
اگر مشخص شد مشکل موقتی بوده است، میتوان Job را دوباره اجرا کرد.
برای Retry کردن یک Job:
php artisan queue:retry ID
برای مثال:
php artisan queue:retry 5
همچنین میتوان بسته به نیاز، چند Job یا همه Jobهای شکستخورده را دوباره پردازش کرد.
# حذف Failed Job
اگر دیگر نیازی به یک Failed Job نداریم، میتوان آن را حذف کرد:
php artisan queue:forget ID
و در صورت نیاز میتوان Failed Jobs را پاکسازی کرد.
# متد failed در Job
گاهی لازم است وقتی Job به صورت نهایی شکست خورد، یک عملیات خاص انجام شود.
میتوانیم متد failed() را تعریف کنیم:
public function failed(?Throwable $exception): void
{
// عملیات بعد از شکست نهایی Job
}
برای مثال میتوانیم در این قسمت:
- Log ثبت کنیم
- به مدیر سیستم Notification ارسال کنیم
- وضعیت سفارش را تغییر دهیم
- یک خطا در سیستم ثبت کنیم
مثلاً:
public function failed(?Throwable $exception): void
{
Log::error('پردازش سفارش شکست خورد.', [
'order_id' => $this->orderId,
'error' => $exception?->getMessage(),
]);
}
# Job Chaining چیست؟
گاهی چند Job باید به ترتیب مشخص اجرا شوند.
مثلاً:
ساخت سفارش
↓
پردازش پرداخت
↓
ساخت فاکتور
↓
ارسال ایمیل
در این حالت میتوان Jobها را Chain کرد.
مثلاً:
Bus::chain([
new ProcessPayment($order->id),
new GenerateInvoice($order->id),
new SendInvoiceEmail($order->id),
])->dispatch();
در این معماری Job بعدی پس از موفقیت Job قبلی اجرا میشود.
این روش برای Workflowهای چندمرحلهای بسیار کاربردی است.
# Job و Event چه تفاوتی دارند؟
Job و Event هر دو برای جدا کردن منطق برنامه استفاده میشوند، اما هدف آنها یکی نیست.
Event معمولاً بیان میکند که یک اتفاق در سیستم رخ داده است.
مثلاً:
OrderCreated
سپس چند Listener میتوانند به این Event واکنش نشان دهند.
اما Job معمولاً بیانکننده یک کار مشخص برای انجام دادن است.
مثلاً:
GenerateInvoice
بنابراین:
| Event | Job |
|---|---|
| بیانگر وقوع یک اتفاق | بیانگر انجام یک کار |
| میتواند چند Listener داشته باشد | یک عملیات مشخص را اجرا میکند |
| برای Event-driven Architecture مناسب است | برای پردازش عملیات مناسب است |
| مثال: OrderCreated | مثال: GenerateInvoice |
این دو حتی میتوانند در کنار یکدیگر استفاده شوند.
مثلاً:
OrderCreated Event
↓
SendInvoiceEmail Listener
↓
GenerateInvoice Job
# Job و Notification چه تفاوتی دارند؟
Notification مشخصاً برای اطلاعرسانی به کاربر طراحی شده است.
مثلاً:
سفارش شما با موفقیت ثبت شد.
اما Job میتواند هر نوع عملیات پردازشی را انجام دهد.
برای مثال:
GenerateInvoice Job
ProcessImage Job
SyncProducts Job
SendReport Job
بنابراین Notification میتواند خودش نیز از Queue استفاده کند، اما مفهوم آن با Job یکسان نیست.
# یک مثال واقعی: پردازش سفارش
فرض کنیم بعد از ثبت سفارش باید پردازشهای مختلفی انجام شود.
ابتدا Job را ایجاد میکنیم:
php artisan make:job ProcessOrder
سپس:
<?php
namespace App\Jobs;
use App\Models\Order;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class ProcessOrder implements ShouldQueue
{
use Queueable;
public int $tries = 3;
public int $timeout = 120;
public function __construct(
public int $orderId
) {}
public function handle(): void
{
$order = Order::find($this->orderId);
if (!$order) {
return;
}
$order->update([
'status' => 'processing',
]);
// پردازشهای مربوط به سفارش
}
}
حالا بعد از ثبت سفارش:
ProcessOrder::dispatch($order->id);
اگر Queue Worker در حال اجرا باشد، Job پردازش خواهد شد.
# یک اشتباه مهم: ارسال اطلاعات بیش از حد به Job
یکی از اشتباهات رایج این است که حجم زیادی از اطلاعات را داخل Job قرار دهیم.
مثلاً به جای:
ProcessOrder::dispatch($order->id);
یک آبجکت بسیار بزرگ یا مجموعه بزرگی از دادهها را به Job ارسال کنیم.
بهتر است تا حد امکان اطلاعات ضروری را ارسال کنیم:
public function __construct(
public int $orderId
) {}
و اطلاعات مورد نیاز را در handle() دریافت کنیم.
این موضوع مخصوصاً در پروژههای بزرگ اهمیت بیشتری پیدا میکند.
# یک اشتباه مهم: وابسته کردن Job به دادهای که ممکن است تغییر کند
فرض کنید Job فقط شناسه سفارش را دریافت میکند:
ProcessOrder::dispatch($order->id);
اما Job چند دقیقه بعد اجرا میشود.
در این فاصله ممکن است وضعیت سفارش تغییر کرده باشد.
بنابراین Job نباید فرض کند دادهای که در زمان Dispatch وجود داشته، حتماً هنگام اجرای Job نیز همان مقدار را دارد.
بهتر است هنگام اجرای Job وضعیت فعلی را بررسی کنیم:
$order = Order::find($this->orderId);
if (!$order) {
return;
}
if ($order->status !== 'pending') {
return;
}
این موضوع از اجرای اشتباه Jobها جلوگیری میکند.
# Job و Database Transaction
یکی از نکات مهم در پروژههای واقعی، اجرای Job در کنار Transactionهای دیتابیس است.
فرض کنید ابتدا یک سفارش را داخل Transaction ایجاد میکنیم:
DB::transaction(function () use ($request) {
$order = Order::create([
'user_id' => auth()->id(),
'total' => $request->total,
]);
ProcessOrder::dispatch($order->id);
});
اگر Job خیلی سریع توسط Worker اجرا شود، باید دقت کنیم که Transaction هنوز Commit نشده باشد.
در چنین شرایطی Job ممکن است قبل از نهایی شدن اطلاعات دیتابیس اجرا شود.
بنابراین در معماریهای حساس باید زمان Dispatch و Commit شدن Transaction را بهدرستی مدیریت کنیم.
این موضوع بهخصوص در پروژههایی که Queue Worker دائماً فعال است اهمیت زیادی دارد.
# Job Idempotency چیست؟
یکی از مفاهیم مهم در سیستمهای Queue این است که Job ممکن است بیش از یک بار اجرا شود.
مثلاً:
تلاش اول → شکست
تلاش دوم → اجرا
یا در شرایط خاص ممکن است یک عملیات دوباره پردازش شود.
بنابراین Jobهای حساس باید تا حد امکان Idempotent طراحی شوند.
یعنی اجرای دوباره آنها نباید باعث ایجاد نتیجه اشتباه یا تکراری شود.
مثلاً اگر قرار است برای یک سفارش فاکتور ایجاد کنیم، قبل از ایجاد فاکتور بررسی کنیم که قبلاً ساخته نشده باشد:
if ($order->invoice) {
return;
}
این موضوع برای عملیاتهایی مانند موارد زیر بسیار مهم است:
- پرداخت
- ثبت تراکنش
- ارسال پیامک
- ارسال ایمیل
- ایجاد فاکتور
- ارتباط با API خارجی
- تغییر موجودی
# Job Middleware چیست؟
Laravel امکان استفاده از Middleware برای Jobها را نیز فراهم میکند.
Job Middleware میتواند برای کنترل نحوه اجرای Job استفاده شود.
برای مثال:
- جلوگیری از اجرای همزمان چند Job
- محدود کردن نرخ اجرا
- کنترل دسترسی
- مدیریت شرایط خاص قبل از اجرای Job
این قابلیت در پروژههای بزرگتر بسیار کاربردی میشود.
برای مثال فرض کنید نمیخواهیم یک سفارش همزمان توسط دو Job پردازش شود.
در چنین شرایطی میتوان از سازوکارهای جلوگیری از اجرای همزمان استفاده کرد.
# Jobهای سنگین را چگونه طراحی کنیم؟
اگر یک Job بیش از حد بزرگ باشد، بهتر است آن را به چند Job کوچکتر تقسیم کنیم.
به جای:
ProcessEverythingJob
که همه کارها را انجام میدهد، بهتر است داشته باشیم:
ProcessOrderJob
GenerateInvoiceJob
SendEmailJob
UpdateStatisticsJob
این معماری چند مزیت دارد:
- Debug کردن آسانتر
- Retry مستقل
- مدیریت خطای بهتر
- مقیاسپذیری بیشتر
- خوانایی کد بهتر
# Job چه زمانی انتخاب مناسبی است؟
Job گزینه بسیار خوبی برای کارهایی است که:
- زمانبر هستند.
- نیاز به اجرای پسزمینه دارند.
- ممکن است با خطا مواجه شوند.
- نیاز به Retry دارند.
- تعداد اجرای آنها زیاد است.
- به API خارجی متصل میشوند.
- پردازش فایل انجام میدهند.
- گزارش تولید میکنند.
- Notification یا Email ارسال میکنند.
# چه کارهایی را نباید بیدلیل داخل Job قرار دهیم؟
Job نباید تبدیل به محل قرار دادن تمام منطق برنامه شود.
برای مثال اگر یک Job شامل چند صد خط کد باشد و تمام Business Logic برنامه داخل آن قرار گرفته باشد، احتمالاً معماری مناسبی نداریم.
در چنین شرایطی بهتر است Business Logic را به Service منتقل کنیم:
Job
↓
Service
↓
Repository / Model
↓
Database
این ساختار نگهداری پروژه را سادهتر میکند.
# تفاوت اجرای مستقیم و اجرای Job
فرض کنیم میخواهیم یک فایل را پردازش کنیم.
اجرای مستقیم
$this->processFile($file);
در این حالت Request منتظر پایان عملیات میماند.
اجرای با Job
ProcessFile::dispatch($file->id);
در این حالت میتوانیم عملیات را به Queue بسپاریم تا Worker آن را پردازش کند.
بنابراین برای عملیات سنگین معمولاً معماری دوم انتخاب مناسبتری است.
# دستورات مهم مربوط به Job و Queue
برخی از دستورات مهمی که هنگام کار با Jobها با آنها مواجه میشویم:
php artisan make:job ProcessOrder
ساخت Job.
php artisan queue:work
اجرای Queue Worker.
php artisan queue:failed
نمایش Jobهای شکستخورده.
php artisan queue:retry all
تلاش مجدد برای Jobهای شکستخورده.
php artisan queue:forget ID
حذف یک Failed Job.
php artisan queue:flush
پاک کردن Failed Jobها.
جزئیات کامل راهاندازی و مدیریت Queue را در مقاله اختصاصی Queue بررسی خواهیم کرد.
# ساختار پیشنهادی برای Jobهای حرفهای
در پروژههای کوچک ممکن است Job بسیار ساده باشد:
class SendEmail implements ShouldQueue
{
public function handle(): void
{
// ارسال ایمیل
}
}
اما در پروژههای بزرگتر بهتر است معماری به شکل زیر باشد:
Controller
↓
Dispatch Job
↓
Job
↓
Service
↓
Model / Repository
↓
Database / API
این ساختار باعث میشود مسئولیت هر بخش مشخص باشد.
# نکات مهم برای طراحی Job در پروژههای واقعی
هنگام طراحی Job بهتر است این موارد را در نظر بگیریم:
۱. Job را کوچک و مشخص نگه دارید
هر Job بهتر است یک مسئولیت مشخص داشته باشد.
۲. دادههای غیرضروری ارسال نکنید
تا حد امکان ID یا دادههای ضروری را به Job منتقل کنید.
۳. Retry را آگاهانه تنظیم کنید
برای هر Job تعداد تلاش مناسب مشخص کنید.
۴. Timeout تعیین کنید
Job نباید بدون محدودیت زمانی اجرا شود.
۵. عملیات حساس را Idempotent کنید
مخصوصاً عملیات مالی و ارتباط با APIها.
۶. خطاها را Log کنید
در پروژههای Production بدون Log مناسب، پیدا کردن مشکل Jobها بسیار دشوار میشود.
۷. Jobهای خیلی بزرگ را تقسیم کنید
اگر یک Job چند مسئولیت دارد، آن را به چند Job یا Service تقسیم کنید.
۸. وضعیت داده را هنگام اجرا بررسی کنید
نباید فرض کنیم اطلاعات هنگام اجرای Job دقیقاً همان اطلاعات زمان Dispatch است.
# آیا هر Job باید با Queue اجرا شود؟
خیر.
Job یک ساختار برای تعریف یک عملیات است و نحوه اجرای آن به تنظیمات و معماری برنامه بستگی دارد.
اگر Queue روی sync باشد، Job میتواند همان لحظه اجرا شود.
اگر Queue روی Driver مناسب تنظیم شده باشد، Job میتواند وارد صف شود و Worker آن را در پسزمینه پردازش کند.
بنابراین:
Job ≠ Queue
اما Job و Queue معمولاً در کنار یکدیگر استفاده میشوند.
# بهترین مثالهای استفاده از Job در Laravel
در پروژههای واقعی، کاربردهای زیادی برای Job وجود دارد:
| کاربرد | مثال Job |
|---|---|
| ایمیل | SendWelcomeEmail |
| سفارش | ProcessOrder |
| فاکتور | GenerateInvoice |
| فایل | ProcessUploadedFile |
| تصویر | ResizeImage |
| API | SyncProducts |
| گزارش | GenerateReport |
| Notification | SendUserNotification |
| پرداخت | VerifyPayment |
| دادهها | ImportProducts |
این موضوع باعث میشود Job یکی از ابزارهای بسیار مهم Laravel برای ساخت برنامههای مقیاسپذیر باشد.
# سوالات متداول درباره Job در Laravel
Job در Laravel چیست؟
Job یک کلاس مستقل برای انجام یک عملیات مشخص در Laravel است که میتواند به صورت مستقیم یا از طریق Queue اجرا شود.
چگونه در Laravel یک Job بسازیم؟
با دستور زیر:
php artisan make:job ProcessOrder
چگونه Job را اجرا کنیم؟
معمولاً با:
ProcessOrder::dispatch($order->id);
ShouldQueue چه کاری انجام میدهد؟
ShouldQueue مشخص میکند که Job برای اجرای از طریق سیستم Queue آماده است.
آیا Job همیشه در پسزمینه اجرا میشود؟
خیر. اگر Queue روی sync باشد، Job میتواند همان لحظه اجرا شود. برای اجرای واقعی در پسزمینه باید Queue و Worker مناسب تنظیم شوند.
چگونه تعداد Retry یک Job را مشخص کنیم؟
مثلاً:
public int $tries = 3;
Failed Job چیست؟
Jobای است که پس از تلاشهای مجاز همچنان با شکست مواجه شده و برای بررسی یا Retry در سیستم Failed Jobs قرار گرفته است.
آیا میتوان Jobها را زنجیرهای اجرا کرد؟
بله. Laravel امکان Job Chaining را فراهم میکند تا چند Job به ترتیب مشخص اجرا شوند.
آیا Job برای عملیات سنگین مناسب است؟
بله. یکی از مهمترین کاربردهای Job انتقال عملیات سنگین و زمانبر از Request اصلی به پردازش پسزمینه است.
# جمعبندی
Job یکی از مفاهیم مهم Laravel برای مدیریت عملیات قابلپردازش در برنامه است.
با استفاده از Job میتوانیم عملیاتهایی مانند:
- پردازش سفارش
- ارسال ایمیل
- تولید فاکتور
- پردازش فایل
- ارتباط با API
- تولید گزارش
- ارسال Notification
را از منطق اصلی Request جدا کنیم.
ساخت یک Job معمولاً با Artisan انجام میشود:
php artisan make:job ProcessOrder
سپس Job را میتوان با:
ProcessOrder::dispatch($order->id);
اجرا کرد.
برای پردازش غیرهمزمان نیز Job میتواند از ShouldQueue استفاده کند و توسط Queue Worker پردازش شود.
در پروژههای حرفهای، فقط ساخت Job کافی نیست؛ باید موضوعاتی مانند Retry، Timeout، Failed Jobs، Idempotency، Transaction و تقسیم صحیح مسئولیتها را نیز در نظر گرفت.
در واقع میتوان معماری کلی را اینگونه در نظر گرفت:
User Request
↓
Controller
↓
Dispatch Job
↓
Queue
↓
Worker
↓
Job
↓
Service
↓
Database / API





