Cara Membuat Scheduler dan Job Queues di Laravel

Cara Membuat Scheduler dan Job Queues di Laravel
Konten Halaman

Laravel menyediakan fasilitas untuk beberapa arsitektur dan struktur aplikasi yang kurang umum maka kita akan bahasa salah satunya yaitu Jobs dan Queue. Dalam tulisan ini kita akan membahas salah satu fitur Laravel untuk mengimplementasikan sebuah data antrian, proses pekerjaan yang mengantri, event(acara), dan publishing(penerbitan) WebSocket dan juga akan membahas Laravel penjadwal, yang membuat jadwal cron.

Untuk memahami apa itu Queue(antrian), pikirkan saja gagasan “mengantri” dalam antrian di bank. Bahkan jika ada beberapa baris—antrian—hanya satu orang yang dilayani pada satu waktu dari setiap antrian, dan setiap orang pada akhirnya akan mencapai bagian depan loket untuk dilayani.Di beberapa bank, ini adalah jenis kebijakan first-in-first-out FIFO yang ketat, tetapi di bank lain, tidak ada jaminan pasti bahwa seseorang tidak akan memotong antrian anda di beberapa titik.

Pada dasarnya, seseorang dapat ditambahkan ke antrian, dihapus dari antrian sebelum waktunya, atau berhasil “diproses” dan kemudian dihapus. Seseorang bahkan mungkin mencapai bagian depan antrian, tidak dapat dilayani dengan benar, kembali ke antrian untuk sementara waktu, dan kemudian diproses lagi.

Antrian dalam pemrograman sangat mirip. Aplikasi anda menambahkan “pekerjaan” ke antrian, yang merupakan sebuah potongan pengkode yang memberi tahu aplikasi cara melakukan perilaku tertentu Kemudian beberapa struktur aplikasi terpisah lainnya, biasanya Job Worker(pekerja antrian) mengambil tanggung jawab untuk pekerjaan dari antrian satu per satu dan melakukan tugas sesuai perintah. Pekerja antrian dapat menghapus pekerjaan, mengembalikannya ke antrian dengan penundaan, atau menandainya sebagai berhasil diproses.

Laravel memudahkan untuk layanan fitur antrian bisa menggunakan Redis, beanstalkd, Amazon Simple Queue Service (SQS), atau tabel database kalian juga dapat memilih driver sinkronisasi agar pekerjaan berjalan langsung di aplikasi anda tanpa benar-benar diantrekan.

di studicase ini saya menggunakan sebuah project cara memebuat api di laravel yang digunakan seblumnya di postingan Tutorial Laravel: Membuat REST API CRUD dengan Laravel serta JWT

Menyiapkan Database

Membuat Migration

disini menggunakan migrate laravel untuk keperluan membuat table dan kolom di database, berikut ini kode dan artisan command yang digunakan:

menjalankan generator migrate laravel

php artisan make:migration task
php artisan make:migration task_scheduler

isi kode migrate yang digunakan untuk task:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class Task extends Migration
{

    public function up()
    {
        Schema::create('task', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('description');
            $table->integer('user_id');
            $table->enum('status', ['WAIT', 'DONE'])->default('WAIT');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::drop('task');
    }
}

isi kode migrate yang digunakan untuk task_scheduler:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class TaskScheduler extends Migration
{

    public function up()
    {
        Schema::create('task_scheduler', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('days');
            $table->time('time');
            $table->string('description');
            $table->integer('user_id');
            $table->text('log_executed');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::drop('task_scheduler');
    }
}

Penjelasan :

  • Schema::create('task_scheduler', function (Blueprint $table) : membuat sebuah tabel baru ketika command migrate dijalankan
  • $table->id(); : membuat kolom id dengan auto increment
  • $table->string('name'); : kolom name dengan tipe VARCHAR dan akan menciptakan sebuah tipe text di database
  • $table->string('days'); : sama seperti name akan membuat tipe menjadi VARCHAR di database mysql
  • $table->time('time'); : membuat kolom dengan name time dan memiliki tipe data TIME
  • $table->string('description'); : sama seperti kolom name dan days akan menciptakan tipe VARCHAR dan name kolom description
  • $table->integer('user_id'); : membuat kolom dengan name user_id dan memiliki tipe INT
  • $table->timestamps(); : disini untuk menggenerate sebuah kolom create_at danupdate_at secara otomatis dengan data type TIMESTAMP

setelah file migrate dirasasa sudah sesuai dengan kebutuhan maka lanjut untuk menjalankan command berikut:

php artisan migrate

Membuat Model

lanjut membuat model laravel untuk mapping data yang bisa di modif atau data yang akan digunakan di model laravel, maka kita buat sebuah model dengan nama TaskScheduler.php yang ada di ...\app\Models\TaskScheduler.php, dengan isi kode berikut ini :

  • Model Task
    kita gunakan artisan untuk mengenerate model
php artisan make:model Task
  • edit file seperti berikut yang berada di \app\Models\Task.php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    use HasFactory;
    protected $table = 'task';

    protected $fillable = [
        'name','description','user_id','status'
    ];
}
  • Model Task Scheduler kita gunakan artisan untuk mengenerate model
php artisan make:model TaskScheduler
  • edit file seperti berikut yang berada di \app\Models\TaskScheduler.php
// TaskScheduler.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class TaskScheduler extends Model
{
    use HasFactory;
    protected $table = 'task_scheduler';

    protected $fillable = [
        'name','days','time','description','user_id','log_executed',
    ];
}

Membuat Repository

Dan disini menggunakan Repository Pattern yang artinya untuk mengakses ke model harus melalui repository terlebih dahulu, mari kita buat sebuah repository dengan nama TaskScheduleRepository.php di folder path ...\app\Repositories\TaskScheduleRepository.php, berikut isi pengkodean repository untuk TaskScheduleRepository.php dan TaskRepository.php:

Task Repository

  • membuat file repo yang berada di ..\app\Repositories\TaskRepository.php
// TaskRepository.php
<?php

namespace App\Repositories;

use App\Models\Task;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;

class TaskRepository
{
    public function create($input)
    {
        try {
            $task = Task::create($input);

            if ($task->save()) {
                $result['status']   = true;
                $result['code']     = 201;
                $result['message']  = "Berhasil Membuat Task Scheduler baru";

                return $result;
            } else {
                $result['status']   = false;
                $result['code']     = 400;
                $result['message']  = "Gagal Membuat Task Scheduler baru";
                $result['task']     = (object)[];
                return $result;
            }
        } catch (\Exception $e) {
            $result['status']       = false;
            $result['code']         = 500;
            $result['message']      = "Gagal Membuat Task Scheduler baru";
            $result['task']         = (object)[];
            $result['error'] = [
                'message' => $e->getMessage(),
                'file' => $e->getFile(),
                'line_of_code' => $e->getLine(),
                'code' => $e->getCode(),
            ];
            return $result;
        }
    }
}

Task Schedule Repository

  • membuat file repo yang berada di ..\app\Repositories\TaskScheduleRepository.php
// TaskScheduleRepository.php
<?php

namespace App\Repositories;

use App\Models\TaskScheduler;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;

class TaskScheduleRepository
{
    private $user;
    public function __construct()
    {
        $this->user = Auth::user();
    }

    public function getById($id)
    {
        try {
            $repoTask             = TaskScheduler::where('user_id', $this->user->id)->where('id', $id)->get();
            $result['code']       = 200;
            $result['status']     = true;
            $result['message']    = "Berhasil Memuat task";
            $result['task']       = $repoTask;
            return $result;
        } catch (\Exception $e) {
            $result['status']       = false;
            $result['code']         = 500;
            $result['message']      = "Gagal Memuat semua task";
            $result['user']         = (object)[];
            $result['error'] = [
                'message' => $e->getMessage(),
                'file' => $e->getFile(),
                'line_of_code' => $e->getLine(),
                'code' => $e->getCode(),
            ];
            return $result;
        }
    }

    public function getAll()
    {

        try {
            $repoTask             = TaskScheduler::where('user_id', $this->user->id)->get();
            $result['code']       = 200;
            $result['status']     = true;
            $result['message']    = "Berhasil Memuat task";
            $result['task']       = $repoTask;
            return $result;
        } catch (\Exception $e) {
            $result['status']       = false;
            $result['code']         = 500;
            $result['message']      = "Gagal Memuat semua task";
            $result['user']         = (object)[];
            $result['error'] = [
                'message' => $e->getMessage(),
                'file' => $e->getFile(),
                'line_of_code' => $e->getLine(),
                'code' => $e->getCode(),
            ];
            return $result;
        }
    }

    public function create($input)
    {
        try {
            $input['days']      = implode(',', $input['days']);
            $input['user_id']   = $this->user->id;

            $task = TaskScheduler::create($input);

            if ($task->save()) {
                $result['status']   = true;
                $result['code']     = 201;
                $result['message']  = "Berhasil Membuat Task Scheduler baru";

                return $result;
            } else {
                $result['status']   = false;
                $result['code']     = 400;
                $result['message']  = "Gagal Membuat Task Scheduler baru";
                $result['user']     = (object)[];
                return $result;
            }
        } catch (\Exception $e) {
            $result['status']       = false;
            $result['code']         = 500;
            $result['message']      = "Gagal Membuat Task Scheduler baru";
            $result['user']         = (object)[];
            $result['error'] = [
                'message' => $e->getMessage(),
                'file' => $e->getFile(),
                'line_of_code' => $e->getLine(),
                'code' => $e->getCode(),
            ];
            return $result;
        }
    }

    public function update($input = [])
    {
        $task = TaskScheduler::find($input['id']);

        try {
            $input['days'] = implode(',', $input['days']);
            if (!is_null($task) && $task->update($input)) {
                $result['code']       = 200;
                $result['status']     = true;
                $result['message']    = "Berhasil Update task";
                $result['task']       = $task;
            } else {
                $result['code']       = 400;
                $result['status']     = false;
                $result['message']    = "Gagal Update task";
                $result['task']       = (object)[];
            }
            return $result;
        } catch (\Exception $e) {
            $result['status']       = false;
            $result['code']         = 500;
            $result['message']      = "Gagal Update task";
            $result['task']         = (object)[];
            $result['error'] = [
                'message' => $e->getMessage(),
                'file' => $e->getFile(),
                'line_of_code' => $e->getLine(),
                'code' => $e->getCode(),
            ];
            return $result;
        }
    }

    public function delete($id)
    {
        $task = TaskScheduler::find($id);

        try {
            if ($task->delete($id)) {
                $result['code']       = 200;
                $result['status']     = true;
                $result['message']    = "Berhasil Delete task";
            }

            return $result;
        } catch (\Exception $e) {
            $result['status']       = false;
            $result['code']         = 500;
            $result['message']      = "Gagal Delete task";
            $result['user']         = (object)[];
            $result['error'] = [
                'message' => $e->getMessage(),
                'file' => $e->getFile(),
                'line_of_code' => $e->getLine(),
                'code' => $e->getCode(),
            ];
            return $result;
        }
    }
}

Membuat Jobs di Laravel

berikut ini perintah Artisan untuk membuat sebuah Job:

php artisan make:job TaskSchedulerJob

Penjelasan :

  • perintah diatas untuk membuat sebuah Job bernama TaskScheduler yang dimana nantinya akan menciptakan sebuah file php TaskSchedulerJob.php yang bertempat di ...\app\Jobs\TaskSchedulerJob.php, tujuannya kita akan membuat sebuah task todo di jam tertentu secara otomatis.

Membuat Endpoint API di Laravel

Menggenerate Controller

setelah selesai menyiapkan untuk kebutuhan database maka lanjut untuk membuat sebuah endpoint yang dimana membutuhkan sebuah controller dan route. mari kit buat controller dengan command php artisan laravel seperti berikut :

php artisan make:controller TaskScheduleController --resource

Penjelasan :

  • dengan imbuhan --resource diakhir command menandakan bahwa controller akan di create dengan isian function methode index create,store,show,edit,update, dan destroy akan tetapi tidak semua akan kita gunakan yang digunakan hanya index, create, update, dan destroy.

Pengkodean Controller

berikut ini kode controller yang digunakan untuk membuat API di laravel :

  • pengkodengan untuk controller TaskScheduleController yang berada di file\app\Http\Controllers\TaskScheduleController.php
// TaskScheduleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use App\Repositories\TaskScheduleRepository;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Log;
use DateTimeZone;
use App\Models\TaskScheduler;
use App\Jobs\TaskSchedulerJob;
use PhpParser\Node\Stmt\Echo_;
use Symfony\Component\Console\Input\Input;

class TaskScheduleController extends Controller
{

    public function index()
    {
        $repoTask   = new TaskScheduleRepository;
        $response   = $repoTask->getAll();
        $statusCode = $response['code'];
        unset($response['code']);

        return response()->json($response, $statusCode);
    }

    public function create(Request $request)
    {
        $inputRequest = $request->input();
        $validator = Validator::make($inputRequest, [
            'name' => 'required|string',
            'days' => 'required|array|in:Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday',
            'time' => 'required|string|date_format:H:i',
            'description' => 'required|string',
            'user_id' =>  'required|string',
        ]);

        if ($validator->fails()) {
          $error                = $validator->messages()->first();
          $response['status']   = false;
          $response['message']  = $error;
          return response()->json($response, 400);
        }

        $repoTask   = new TaskScheduleRepository;
        $response   = $repoTask->create($inputRequest);
        $statusCode = $response['code'];
        unset($response['code']);

        return response()->json($response, $statusCode);
    }

    public function store(Request $request)
    {
        //
    }

    public function show($id)
    {
        $repoTask   = new TaskScheduleRepository;
        $response   = $repoTask->getById($id);
        $statusCode = $response['code'];
        unset($response['code']);

        return response()->json($response, $statusCode);
    }

    public function edit($id)
    {
        //
    }

    public function update(Request $request, $id)
    {
        $inputRequest = $request->input();
        $inputRequest['id'] = $id ?? null;
        $validator = Validator::make($inputRequest, [
            'id'   => 'required|string',
            'name' => 'required|string',
            'days' => 'required|array|in:Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday',
            'time' => 'required|string|date_format:H:i',
            'description' => 'required|string',
            'user_id' =>  'required|string',
        ]);

        if ($validator->fails()) {
          $error                = $validator->messages()->first();
          $response['status']   = false;
          $response['message']  = $error;
          return response()->json($response, 400);
        }

        $repoTask   = new TaskScheduleRepository;
        $response   = $repoTask->update($inputRequest);
        $statusCode = $response['code'];
        unset($response['code']);

        return response()->json($response, $statusCode);
    }

    public function destroy($id)
    {
        //
    }

    public function handleJob()
    {
        \Log::info(Carbon::now());
        $day = ucwords(\Carbon\Carbon::now()->Format('l'));
        $time =  \Carbon\Carbon::now()->setTimezone(new DateTimeZone('+7'))->format('H:i');

        $response = TaskScheduler::where('days', 'like', '%'.$day.'%')
            ->where('time', '=', $time)
            ->get();


        $response = $response->toArray();
        $executed_schedulers = [];

        if (count($response) > 0) {
            foreach ($response as $key => $data) {
                $_taskScheduleId = $data['id'];
                $_taskUserId = $data['user_id'];
                $_taskData = [
                    'name' => $data['name'],
                    'description' => $data['description'],
                    'user_id' => $data['user_id'],
                ];
                /* proses queue terjadi disini */
                TaskSchedulerJob::dispatch($_taskScheduleId, $_taskUserId, $_taskData)->onQueue('task_scheduler');
                /* end queue */
                $executed_schedulers[] =  $_taskScheduleId;
            }
        }

        $total_executed_scheduler = count($executed_schedulers);

        if ($total_executed_scheduler > 0) {
            $result['total_executed_scheduler'] = $total_executed_scheduler;
            $result['executed_idSchedulers'] = $executed_schedulers;
            $result['status'] = 'success';
        } else {
            $result['status'] = 'empty';
        }

        return $result;
    }
}

Penjelasan :

  • method handleJob di controller tujuannya untuk mentrigger dari cron ketika dipanggil, contohnya seperti berikut yang ada di ...\app\Console\Kernel.php:
    ...
    protected function schedule(Schedule $schedule)
    {
        $schedule->call('\App\Http\Controllers\TaskScheduleController@handleJob')->everyMinute();
    }
    ...
  • yang dimana pengkodean di atas akan melakukan exsekusi setiap menitnya oleh sistem cron di sistem operasi kalian.
  • dan selain itu kalian bisa mentriger tanpa harus menunggu cron, misalnya kesulitan untuk mencoba di environment testing atau di local kalian. kalian bisa gunakan di route dan panggil dari sana seperti memanggil controller pada umumnya.

Menambahkan route di api.php

Langsung saja pengkodeannya membuat sebuah API di Laravel untuk keperluan sebuah data task sebagai berikut:

setelah selesai pengkodean controller mari kita definisikan sebuah route endpoint yang akan kita gunakan, kita modifikasi file route\api.php seperti berikut:

use App\Http\Controllers\TaskScheduleController;

Route::name('taskSchedule')->get('task-shcedule', [TaskScheduleController::class, 'index']);
Route::name('taskSchedule.create')->post('task-shcedule', [TaskScheduleController::class, 'create']);
Route::name('taskSchedule.detail')->get('task-shcedule/{id}', [TaskScheduleController::class, 'show']);
Route::name('taskSchedule.update')->put('task-shcedule/{id}', [TaskScheduleController::class, 'update']);
Route::name('taskSchedule.patch')->patch('task-shcedule/{id}', [TaskScheduleController::class, 'patch']);
Route::name('taskSchedule.delete')->delete('task-shcedule/{id}', [TaskScheduleController::class, 'destroy']);

// untuk memanggil job tanpa menunggu cron jalan vie endpoint task-shcedule/handleJob
Route::name('taskSchedule.handleJob')->delete('task-shcedule/handleJob', [TaskScheduleController::class, 'handleJob']);

Menjalankan Jobs Queue Laravel

sebelum menjalankan kita siapkan command scheduler dengan cara command berikut :

php artisan queue:work --queue=task_scheduler redis --tries=3 --timeout=0

Penjelasan :

  • queue:work : perintah untuk menjalankan queue
  • --queue=task_scheduler redis : disini nama queue task_scheduler dengan menggunakan database redis
  • --tries=3 --timeout=0 : disini mencoba untuk 3 kali jiga mengalami gagal dan timeout 0

Penutup

Perjalanan tutorial masih jauh dari kata sempurna harus dibenahi ok sampai sini masih belum menjalankan jobs schedule nya bisa menghubungi saya atau tunggu di up file projectnya ke github. sampai ketemu lagi terimakasih.

Penjelasan :