Design pattern yang paling berguna di Laravel adalah yang memecahkan masalah nyata di proyekmu: action class untuk logika bisnis yang dipakai ulang, strategy untuk banyak varian perilaku (misalnya beberapa payment gateway), observer atau event untuk efek samping, pipeline untuk proses bertahap, dan DTO untuk data yang berpindah antar lapisan. Laravel sendiri sudah memakai banyak pola ini di intinya, jadi kamu tidak perlu membangunnya dari nol.
Prinsip Memilih Design Pattern
- Mulai sederhana. Controller yang memanggil model langsung itu wajar untuk CRUD biasa.
- Tambah pola saat ada rasa sakit: kode yang diduplikasi, if-else yang terus bertambah, atau logika yang sulit diuji.
- Ikuti konvensi tim. Pola yang konsisten lebih berharga daripada pola yang "paling benar".
1. Action Class
Satu class, satu aksi bisnis. Cocok untuk logika yang dipanggil dari controller, command, job, dan test.
class RegisterMember
{
public function handle(array $data): User
{
return DB::transaction(function () use ($data) {
$user = User::create($data);
$user->assignRole('member');
event(new MemberRegistered($user));
return $user;
});
}
}
// pemakaian
app(RegisterMember::class)->handle($request->validated());
Kelebihannya, nama class menjelaskan apa yang terjadi dan mudah ditemukan di folder app/Actions.
2. Service Class
Kumpulan operasi yang berhubungan dengan satu domain atau integrasi, misalnya ShippingService yang menghitung ongkir dan melacak resi dari API kurir. Bedanya dengan action: service berisi beberapa method yang saling terkait.
3. Strategy
Untuk perilaku yang punya banyak varian dengan antarmuka sama. Contoh klasik: beberapa payment gateway.
interface PaymentGateway
{
public function charge(Order $order): PaymentResult;
}
class MidtransGateway implements PaymentGateway { /* ... */ }
class XenditGateway implements PaymentGateway { /* ... */ }
// AppServiceProvider
$this->app->bind(PaymentGateway::class, fn () => match (config('payment.driver')) {
'xendit' => new XenditGateway(config('services.xendit.key')),
default => new MidtransGateway(config('services.midtrans.key')),
});
Controller cukup bergantung pada PaymentGateway. Menambah gateway baru tidak mengubah kode yang sudah ada, dan di test kamu bisa mengganti dengan implementasi palsu.
4. Observer dan Event
Untuk efek samping yang tidak boleh membebani alur utama: kirim email, catat log, hapus cache.
// event + listener (bisa di-queue)
event(new OrderPaid($order));
class SendReceiptEmail implements ShouldQueue
{
public function handle(OrderPaid $event): void
{
Mail::to($event->order->customer)->send(new ReceiptMail($event->order));
}
}
Hati-hati dengan observer model yang terlalu banyak: alur menjadi tidak terlihat dari controller, dan debugging lebih sulit. Untuk logika bisnis inti, panggil secara eksplisit.
5. Pipeline
Untuk proses bertahap, misalnya filter pencarian yang bisa ditambah tanpa memperpanjang satu method.
$products = Pipeline::send(Product::query())
->through([
Filters\ByCategory::class,
Filters\ByPriceRange::class,
Filters\InStock::class,
])
->thenReturn()
->paginate(20);
class ByCategory
{
public function handle(Builder $query, Closure $next): Builder
{
if (request()->filled('category')) {
$query->where('category_id', request('category'));
}
return $next($query);
}
}
6. Data Transfer Object (DTO)
Objek sederhana bertipe untuk membawa data antar lapisan, menggantikan array yang kuncinya mudah salah ketik.
final readonly class ShippingQuote
{
public function __construct(
public string $courier,
public string $service,
public int $cost,
public int $etaDays,
) {}
}
Berguna terutama untuk data dari API pihak ketiga, yang strukturnya sering berbeda-beda.
7. Repository: Kapan Perlu?
Repository membungkus akses data di balik interface. Di Laravel, Eloquent sudah merupakan lapisan akses data, sehingga repository untuk setiap model sering hanya menambah file tanpa manfaat. Repository masuk akal kalau:
- Sumber data bisa berganti, misalnya dari database ke API eksternal.
- Query yang sangat kompleks perlu dikumpulkan di satu tempat dan dipakai di banyak lokasi.
Untuk kebutuhan biasa, local scope dan query class kecil sudah cukup.
Ringkasan Kapan Memakai Design Pattern
| Masalah | Pola |
|---|---|
| Logika bisnis dipanggil dari banyak tempat | Action class |
| Banyak varian perilaku dengan antarmuka sama | Strategy |
| Efek samping setelah aksi utama | Event dan listener |
| Proses bertahap yang terus bertambah | Pipeline |
| Array data yang strukturnya membingungkan | DTO |
| Sumber data bisa berganti | Repository |
Kesalahan yang Sering Terjadi
- Pola untuk semua hal sejak hari pertama: interface, repository, dan service untuk CRUD sederhana.
- Service class raksasa berisi puluhan method yang tidak saling berhubungan.
- Logika tersembunyi di observer, sehingga menyimpan model memicu rantai aksi yang tidak terduga.
Langkah Berikutnya
Lihat bagaimana pola-pola ini dipakai dalam aplikasi nyata di studi kasus arsitektur CMS Laravel. Untuk kebiasaan dasar yang melengkapinya, baca Laravel 12 best practices.