• Что бы вступить в ряды "Принятый кодер" Вам нужно:
    Написать 10 полезных сообщений или тем и Получить 10 симпатий.
    Для того кто не хочет терять время,может пожертвовать средства для поддержки сервеса, и вступить в ряды VIP на месяц, дополнительная информация в лс.

  • Пользаватели которые будут спамить, уходят в бан без предупреждения. Спам сообщения определяется администрацией и модератором.

  • Гость, Что бы Вы хотели увидеть на нашем Форуме? Изложить свои идеи и пожелания по улучшению форума Вы можете поделиться с нами здесь. ----> Перейдите сюда
  • Все пользователи не прошедшие проверку электронной почты будут заблокированы. Все вопросы с разблокировкой обращайтесь по адресу электронной почте : info@guardianelinks.com . Не пришло сообщение о проверке или о сбросе также сообщите нам.

Building Elegant Batch Jobs in Laravel with Clean Architecture

Sascha Оффлайн

Sascha

Заместитель Администратора
Команда форума
Администратор
Регистрация
9 Май 2015
Сообщения
1,605
Баллы
155

Introduction


When you need to process millions of records, the naive approach—looping through everything in a single job—quickly fails. A better way is to break processing into small, repeatable batches.

In this article, we’ll build a generic batch job pattern for Laravel that’s:

  • safe to serialize in the queue,
  • dependency-injection friendly,
  • clean and extensible.

No reflection tricks, no magic container calls—just solid code.

The Core Idea


Each batch job:

  1. Executes one iteration (limit items starting from offset).
  2. Reschedules itself if there’s more work to do.
  3. Delegates business logic to a handler implementing a shared interface.
Step 1 — The Handler Interface


namespace App\Foundation\Jobs\Batch;

use Illuminate\Support\Carbon;

interface BatchJobHandler
{
/**
* Process one batch.
* Must return the number of processed items.
*/
public function handle(Carbon $momentAt, int $limit, int $offset): int;
}




Every batch operation implements this interface.
It’s explicit and easy to test.

Step 2 — The Base Class


namespace App\Foundation\Jobs\Batch;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Carbon;
use function dispatch;
use function now;

abstract readonly class BatchJob implements ShouldQueue
{
public function __construct(
public string $momentAt,
public int $limit = 5000,
public int $offset = 0,
) {}

protected function handleAndDispatchNext(BatchJobHandler $handler): void
{
$handledCount = $handler->handle(
Carbon::parse($this->momentAt),
$this->limit,
$this->offset,
);

if ($handledCount < $this->limit) {
return; // no more data
}

dispatch(new static(
$this->momentAt,
$this->limit,
$this->offset + $this->limit
))->delay(now()->addSecond());
}
}




Key points:

  1. Only primitive constructor arguments → safe for queue serialization.
  2. The handler returns how many records were processed.
  3. If the handler processes a full batch, the job automatically requeues the next iteration.
Step 3 — The Handler


namespace App\Modules\Orders\Commands;

use App\Foundation\Jobs\Batch\BatchJobHandler;
use Illuminate\Support\Carbon;
use App\Modules\Orders\Domain\Contracts\OrderRepository;

final readonly class CloseExpiredOrdersBatch implements BatchJobHandler
{
public function __construct(
private OrderRepository $orders,
) {}

public function handle(Carbon $momentAt, int $limit, int $offset): int
{
$expired = $this->orders->findExpired($momentAt, $limit, $offset);

foreach ($expired as $order) {
// either dispatch a job or process directly:
// dispatch(new CloseOrderJob($order->id, $momentAt));

$order->close($momentAt);
$this->orders->update($order);
}

return $expired->count();
}
}




Each handler focuses solely on domain logic for its batch slice.
The framework handles pagination and chaining automatically.

Step 4 — A Concrete Job


namespace App\Modules\Orders\Jobs;

use App\Foundation\Jobs\Batch\BatchJob;
use App\Modules\Orders\Commands\CloseExpiredOrdersBatch;

final readonly class CloseExpiredOrdersBatchJob extends BatchJob
{
public function handle(CloseExpiredOrdersBatch $command): void
{
$this->handleAndDispatchNext($command);
}
}





Laravel’s container automatically injects CloseExpiredOrdersBatch into handle().
The job itself doesn’t contain any business logic—only orchestration.

Step 5 — Kicking It Off


You start it like any other Laravel job:


dispatch(new CloseExpiredOrdersBatchJob(now()->toIso8601String()));




The system runs the first batch, counts processed items,
and queues the next one until no more work remains.

Why This Pattern Works


✅ Serializable: No objects in constructor.
✅ DI-friendly: Laravel injects handlers naturally.
✅ Reusable: The base class fits any batchable use case.
✅ Transparent: No container reflection magic—everything is explicit.

Conclusion


This pattern is a small, composable framework for scalable background processing in Laravel.
It keeps orchestration separate from business logic, uses native Laravel features, and remains fully testable.

If your queues ever need to handle thousands—or millions—of records, this approach will keep your jobs clean, safe, and elegant.



Источник:

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

 
Вверх Снизу