Skip to content
Architecture Programming

Stateless Laravel Core

By Romi Fadli, S.Kom. 13 min read Software Design
On this page
Three identical empty application nodes stacked as copies, connected to a database, a cache, object storage, a pool of queue workers, and a telemetry collector that hold all the state

Stateless Laravel Core adalah cara saya memakai Laravel ketika ia menjadi jantung platform yang dibangun satu tim: aplikasi yang memegang user, organisasi, dan catatan, sementara service khusus mengerjakan pekerjaan berat di sekelilingnya. Laravel sendiri — diciptakan Taylor Otwell dan dibentuk komunitasnya — sudah punya struktur yang baku, dan saya mengikutinya. Artikel ini tentang keputusan yang saya tambahkan di atasnya, supaya inti itu bisa disalin ke berapa pun node, tak pernah membuat user menunggu pekerjaan yang lambat, dan selalu bisa diamati.

Inilah salah satu arsitektur yang saya pakai, dan saya memilihnya demi orang sebanyak demi kode. Bila tim yang akan merawat intinya, saya ingin inti itu ditulis dalam bahasa dan framework yang sudah dikenal sebagian besar dari mereka.

Sekilas

AsalFramework Laravel, dengan kesepakatan saya sendiri untuk menjalankannya sebagai inti yang stateless
Saya pakai untukInti platform yang dirawat tim: banyak domain, banyak user, banyak integrasi
Tak saya pakai untukPekerjaan yang sepenuhnya dalam kendali saya sendiri, atau tim yang semuanya fasih bahasa compiled
BahasaPHP, dengan Laravel
Posisi Stateless Laravel Core di antara arsitektur yang saya pakai.

Gagasannya secara singkat

A. Node tak memegang apa pun

Satu salinan aplikasi yang sedang berjalan tak menyimpan file, session, penghitung, maupun cache miliknya sendiri. Semua yang harus bertahan melewati satu request tinggal di database, di Redis, atau di object storage, jadi node bisa diganti, digandakan, atau dimatikan kapan saja tanpa kehilangan apa pun.

B. Inti mendelegasikan

Pekerjaan yang lambat, khusus, atau milik disiplin lain — penandatanganan, render, storage, sertifikat — menjadi milik service khusus atau job di latar belakang. Inti memutuskan dan mencatat; ia tak mengerjakan semuanya sendiri.

C. Tak ada yang gagal diam-diam

Setiap request, setiap job, dan setiap panggilan ke service lain meninggalkan jejak di satu jalur telemetry, dan inti dirancang supaya masalah muncul di sana sebelum user harus melaporkannya.

Kenapa saya memilihnya

Tim lebih dulu. Inti yang akan dibaca dan diubah banyak orang sepatutnya ditulis dalam bahasa yang sudah dipakai sebagian besar dari mereka. PHP adalah bahasa interpreter yang dikenal luas dan cepat diiterasi, dan Laravel memberi semua orang kesepakatan yang sama untuk routing, validasi, antrean, dan database, sehingga anggota tim baru bisa menemukan jalannya tanpa saya.

Framework-nya sudah baku, jadi tenaga saya tercurah untuk menjalankannya dengan baik. Saya tak merancang ulang struktur Laravel. Yang menentukan apakah aplikasi Laravel tetap sehat di production sebagian besar berada di luar struktur itu: di mana state tinggal, apa yang terjadi pada pekerjaan yang lambat, bagaimana ia di-build dan di-deploy, dan bagaimana ia bisa diamati. Itulah keputusan yang saya ambil dengan sengaja.

Inti yang stateless bertambah besar dengan cara disalin. Bila node tak memegang apa pun, menambah kapasitas berarti menyalakan satu lagi, dan crash tak lebih dari sekadar restart. Sifat itu lebih berharga bagi saya daripada optimasi apa pun di dalam satu proses.

Kesepakatan saya

A. Apa pun yang bisa memblokir dilempar ke job

Mengirim email, menandatangani dokumen, membuat thumbnail, dan memanggil balik sistem lain semuanya berjalan sebagai job di antrean. Request yang memulai pekerjaan panjang langsung dijawab dengan status yang menyatakan pekerjaan sedang berjalan, terpisah dari status yang menyatakan pekerjaan selesai. Job menerima identitas, bukan record utuh, lalu memuat data segar saat dijalankan; rahasia dienkripsi sebelum masuk antrean; dan data yang dibutuhkan job divalidasi sebelum job dimasukkan ke antrean, jadi job tak pernah berangkat dari masukan yang buruk. Tugas sampingan yang gagal, seperti webhook ke client, tak pernah membatalkan pekerjaan utamanya.

B. File tak pernah menyentuh node

Aplikasi tak menulis file ke disk lokal dan tak membawa client S3. Semua storage lewat service storage terpisah, jadi aplikasi tak memegang kredensial storage dan pergantian vendor storage tak sampai menyentuhnya. Browser meng-upload langsung ke storage lewat link berumur pendek, sehingga byte file tak pernah melewati PHP. Upload mendarat di area sementara dan baru dipromosikan ketika menjadi bagian dari satu record, sementara storage sendiri membuang apa pun yang tak pernah dipromosikan. Sebelum apa pun dihapus, satu tempat di kode menjawab apakah objek itu masih dirujuk.

C. State tinggal di Redis dan database

Access token berupa JWT stateless yang ditandatangani private key. Refresh token tinggal di Redis, dirotasi setiap kali dipakai, dan dibawa dalam cookie httpOnly; token yang dicabut dan perangkat yang sedang login juga dicatat di sana. Penghitung rate limit disimpan di Redis, bukan di database, dan sertifikat atau key set yang diambil dari service lain di-cache di Redis, bukan disimpan sebagai file, jadi tak ada node yang butuh apa pun di disknya sendiri.

D. Satu jenis token, siapa pun provider-nya

User bisa login lewat identity provider eksternal, tapi inti tak pernah membiarkan token mereka masuk ke bagian sistem lainnya. Ia menuntaskan login eksternal, memvalidasi identity token provider terhadap key yang diterbitkan provider itu, lalu menerbitkan tokennya sendiri dalam format yang sama dengan setiap login lain. Akun ditautkan lewat subject identifier milik provider, tak pernah lewat alamat e-mail yang belum terverifikasi. Inti menerbitkan signing key-nya sendiri, sehingga service lain bisa memverifikasi tokennya tanpa memanggilnya.

E. Master data di-cache, dan hanya di tempat yang sepadan

Data referensi yang terus dibaca dan jarang diubah di-cache di Redis, diperbarui ketika record disimpan atau dihapus, dan dibaca kembali dengan semua konversi tipenya utuh. Bila Redis tak tersedia, kode kembali ke database dan tak gagal. Tak semuanya di-cache: satu data set besar yang pernah saya cache ternyata hanya dibaca saat suntingan administratif yang jarang, jadi cache-nya saya cabut lagi. Saya mengukur sebelum meng-cache.

F. Runtime yang cepat, di-build dengan cara yang sama setiap kali

Aplikasi berjalan di FrankenPHP dalam worker mode, yang menjaga framework tetap ter-boot di antara request, dengan web server bawaannya melayani HTTP biasa di belakang reverse proxy yang mengakhiri TLS. Setelan PHP yang penting, seperti batas upload, ditetapkan eksplisit, karena image runtime tanpa setelannya sendiri diam-diam kembali ke nilai bawaan yang jauh terlalu kecil. Image di-build bertahap, dengan dependensi di-cache di layer tersendiri dan kode aplikasi disalin paling akhir; satu image di-build sekali dari tag rilis, hanya setelah test lolos, dan image yang sama itulah yang dipromosikan dari environment ke environment.

G. Satu container, diawasi

Tiap container menjalankan web server dan sekumpulan worker antrean berdampingan di bawah process supervisor, sehingga satu image adalah seluruh aplikasi dan menambah salinannya menambah keduanya. Worker didaur ulang setelah sejumlah job, sejumlah waktu, atau batas memori, yang mencegah proses PHP berumur panjang membengkak perlahan. Saat start, container menghangatkan cache konfigurasi, route, dan view, tapi tak pernah menjalankan migration database: dua replika yang start bersamaan akan saling berlomba, jadi migration adalah langkah deployment tersendiri.

ini
[program:web]
command=frankenphp run --config /etc/caddy/Caddyfile
autorestart=true

[program:queue]
command=php artisan queue:work --tries=3 --max-jobs=100 --max-time=3600 --memory=512
numprocs=8
process_name=%(program_name)s_%(process_num)02d
user=www-data
autorestart=true
stopasgroup=true
killasgroup=true

H. Health yang berkata jujur

Health diperiksa di dua tingkat. Di dalam image, satu pemeriksaan memastikan supervisor, web server, dan sedikitnya satu worker sedang berjalan. Di atasnya, endpoint readiness gabungan memeriksa database, Redis, storage, dan setiap service yang dibutuhkan inti, melaporkan masing-masing secara terpisah, dan menjawab 503 bila ada yang mati. Endpoint itu dibebaskan dari rate limit, supaya pemeriksaan milik platform sendiri tak pernah menguncinya. Otomasi yang men-deploy atau me-restart inti hanya bisa dipercaya bila jawaban ini bisa dipercaya.

I. Log dikirim ke collector, bukan ke file

Aplikasi tak mengandalkan file log Laravel di disk lokal. Log, trace, dan metrics dikirim ke collector OpenTelemetry:

  • setiap request mendapat id UUIDv7 yang terurut waktu, dan catatan log-nya dikumpulkan selama request lalu dikirim setelah respons, sehingga telemetry tak pernah menunda user;
  • logger milik Laravel sendiri dijembatani ke jalur yang sama, dengan penjaga terhadap rekursi ketika collector-nya sendiri tak terjangkau;
  • buffer di-flush ketika job selesai, gagal, atau worker-nya berhenti, karena log job yang dimatikan paksa justru yang paling dibutuhkan;
  • exception dicatat di satu tempat, dan pesannya tak pernah sampai ke client;
  • route API selalu menjawab dalam JSON, tak pernah dengan redirect, dan kegagalan validasi dicatat menurut nama field-nya saja, tak pernah nilainya;
  • rahasia diredaksi di satu tempat, event audit ditulis sekali sebagai log record, dan metrics memakai delta temporality;
  • trace satu request berlanjut ke setiap service yang dipanggilnya dan ke job yang dimasukkannya ke antrean.

Aturan di bawahnya sederhana: telemetry gagal terbuka, keamanan gagal tertutup. Collector yang rusak tak boleh merusak request; pemeriksaan yang meragukan tak boleh meloloskannya.

J. Identitas client hanya dari trusted proxy

Alamat client diambil dari header forwarded hanya bila request datang dari proxy di daftar rentang alamat yang eksplisit, tak pernah dari proxy mana saja. Rantai forwarded mentah disimpan sebagai bukti forensik tapi tak pernah dipakai untuk memutuskan apa pun, dan nama atribut yang sama dipakai setiap service, jadi satu query bisa mengikuti satu client melintasi semuanya.

K. Halaman depan dirender di server, aplikasinya terpisah

Halaman beranda publik dirender di server, supaya mesin pencari bisa mengindeksnya tanpa menjalankan JavaScript. Aplikasi yang sungguh dipakai orang bekerja adalah single-page application di repository-nya sendiri, dengan siklus build dan rilisnya sendiri, yang membuat keduanya lebih mudah dirawat dan membiarkan antarmukanya berkembang tanpa menyentuh inti.

L. Konfigurasi yang selamat dari cache dan upgrade

Kode aplikasi membaca setelan lewat file konfigurasi, tak pernah langsung dari environment, karena begitu konfigurasi di-cache, pembacaan environment langsung tak mengembalikan apa pun. Tiap dependensi eksternal punya file konfigurasinya sendiri, dengan komentar yang menjelaskan kenapa tiap nilai seperti itu. Blok konfigurasi bawaan Laravel dibiarkan seperti yang dikirim framework, bahkan yang tak dipakai project, supaya upgrade framework tetap mudah. File environment di-mount saat container start dan tak pernah dibakar ke dalam image.

M. Test yang tak butuh infrastruktur

Test berjalan dengan database in-memory, cache dan rate limiter in-memory, antrean sinkron, serta telemetry dan pemeriksaan eksternal yang dimatikan. Test tak butuh server database, Redis, maupun storage, selesai dalam hitungan detik ketimbang menunggu timeout jaringan, dan tak bisa membocorkan state dari satu test ke test berikutnya.

N. Setiap dependensi di balik satu pintu

Tiap service eksternal dijangkau lewat satu class miliknya sendiri, dengan konfigurasi dan timeout-nya sendiri, dan kegagalannya diterjemahkan menjadi keluarga kecil exception — tak ditemukan, request tak sah, tak tersedia — yang bisa ditindaklanjuti bagian inti lainnya. Bila satu dependensi tak punya fitur yang dibutuhkan inti, saya memintanya ke hulu, tak menambalnya di sisi lokal.

UrusanTempatnya
Pekerjaan lambat atau eksternaljob di antrean
Fileobject storage, lewat service storage dan link berumur pendek
Session, token, penghitung, cacheRedis
Record dan data lookupdatabase relasional
Log, trace, metrics, auditcollector OpenTelemetry
Perubahan skemalangkah deployment, tak pernah saat container start
Tempat tiap urusan di inti Laravel yang stateless.

Cara saya memeriksa service Laravel

BidangPertanyaan sayaSumber
StateBisakah node ini dihapus sekarang juga tanpa kehilangan apa pun?node tak memegang apa pun
FileAdakah kode yang menulis ke disk lokal atau memegang kredensial storage?file tak pernah menyentuh node
Kerja yang memblokirAdakah request yang menunggu email, penandatanganan, render, atau panggilan ke sistem lain?apa pun yang bisa memblokir dilempar ke job
AntreanApakah job membawa identitas, dan rahasia hanya dalam bentuk terenkripsi?apa pun yang bisa memblokir dilempar ke job
TokenApakah setiap login berakhir dengan token milik inti sendiri?satu jenis token
RuntimeApakah setelan runtime eksplisit, dan image di-build sekali lalu dipromosikan?runtime yang cepat
StartApakah container start tanpa menjalankan migration?satu container, diawasi
HealthApakah readiness memeriksa setiap dependensi dan menjawab 503 dengan jujur?health yang berkata jujur
TelemetryBisakah kegagalan di job atau service lain ditemukan tanpa login ke server?log dikirim ke collector
KonfigurasiAdakah kode aplikasi yang membaca environment secara langsung?konfigurasi yang selamat dari cache
Pertanyaan yang saya pakai untuk memeriksa inti Laravel.

Di mana saya tidak memakainya

Ketika pekerjaan itu milik saya sendiri

Laravel adalah pilihan yang dibuat untuk tim. Ketika satu pekerjaan sepenuhnya dalam kendali saya, tanpa orang lain yang harus membaca atau mengubahnya, saya bebas memilih bahasa compiled dan struktur yang cocok dengan masalahnya, bukan dengan timnya.

Ketika seluruh tim menguasai bahasa compiled

Bila setiap orang yang akan merawat satu service memahami Rust, Go, atau bahasa compiled lain beserta konteks yang dibawanya, saya membangunnya dalam bahasa itu. Bila satu saja anggota tim belum menguasainya, intinya tetap di Laravel, karena codebase yang hanya bisa diubah sebagian tim adalah codebase yang tak benar-benar dimiliki tim itu. Service yang saya bangun dengan Rust mengikuti salah satu bentuk ini:

Rujukan

Dokumentasi Laravel, untuk struktur framework itu sendiri, antrean, caching, dan caching konfigurasi. Adam Wiggins, The Twelve-Factor App (2011): proses yang stateless, konfigurasi di environment, log sebagai aliran event, proses yang bisa dibuang, dan satu build yang dipromosikan melewati setiap environment adalah kerabat dekat kesepakatan di atas. Saya menyebutnya sebagai kemiripan, bukan sebagai sumber; kesepakatan di sini tumbuh dari menjalankan satu platform di production.

Istilah

Stateless node
Salinan aplikasi yang berjalan tanpa menyimpan apa pun di antara request, sehingga bisa diganti atau digandakan dengan bebas.
Job di antrean
Satuan pekerjaan yang dicatat di antrean dan dikerjakan worker di latar belakang, tak dikerjakan selagi user menunggu.
Worker mode
Cara menjalankan PHP di mana aplikasi di-boot sekali lalu melayani banyak request, tak di-boot ulang untuk tiap request.
Process supervisor
Program yang menjalankan beberapa proses dalam satu container, menyalakannya kembali saat berhenti, dan menghentikannya bersama-sama.
Gagal terbuka, gagal tertutup
Apakah satu komponen membiarkan pekerjaan berlanjut (terbuka) atau menghentikannya (tertutup) ketika ia tak bisa menjalankan tugasnya sendiri.

Technologies covered

  • PHP
  • Laravel
  • FrankenPHP

About the author

Photo of Romi Fadli

Romi Fadli, S.Kom.

Solution Architect · Backend Engineer · Embedded Systems

In my day job I work as a solution architect. I design the system, lead the team that builds it — quality assurance, backend developers, a UI developer, DevOps and DevSecOps — and write the most critical modules myself, such as a certificate authority, a long-term PDF signer, and the core API of a personnel system.

I work across the whole stack that a running system actually needs. On the hardware side that means PCB design, microcontrollers, and embedded firmware — including RISC-V boards running FreeRTOS and Embassy, speaking MQTT over mutually authenticated TLS on a microcontroller with 64 KB of SRAM.

Most of my day-to-day work is software: Rust backends and API modules built on Clean Architecture, an OAuth2/OpenID Connect authorization server written from scratch, PAdES document signing, and certificate authorities — root CAs, timestamping authorities, and OCSP responders. I also build the frontends that sit on top of them.

Running all of that means infrastructure: Docker Compose stacks, networking, Mikrotik and RADIUS, observability with OpenTelemetry, and a steady habit of writing command-line tools and utilities for Linux when the existing ones do not fit.

Share

Enjoyed this?

Get an email when a new article is published.

Referenced by

The core API at the centre, fed by storage, address, face and preview services, with documents sent across a boundary to an external signing platform

Inti Laravel sistem kepegawaian aparatur sipil negara: record pegawai dan riwayat karier, usulan swalayan yang diperiksa, keputusan kenaikan gaji berkala, kenaikan pangkat, dan pensiun, aturan dari peraturan, bagan organisasi berversi, dan akses berlingkup sampai satu unit.

11 min read Read more →
A central core holding people, organisations, documents, and certificates, connected to eight surrounding services for storage, addresses, certificates, timestamps, the root, signing, thumbnails, and face challenges

Inti Laravel dari platform penandatanganan dokumen: memegang user, identitas, organisasi, sistem client, dokumen beserta versi bertanda tangannya, stempel, dan sertifikat, serta mengorkestrasi service yang menandatangani, menstempel waktu, merender, dan menyimpan — tanpa pernah memegang file.

9 min read Read more →

Related Articles

The core API at the centre, fed by storage, address, face and preview services, with documents sent across a boundary to an external signing platform

Inti Laravel sistem kepegawaian aparatur sipil negara: record pegawai dan riwayat karier, usulan swalayan yang diperiksa, keputusan kenaikan gaji berkala, kenaikan pangkat, dan pensiun, aturan dari peraturan, bagan organisasi berversi, dan akses berlingkup sampai satu unit.

11 min read Read more →