Dalam pengembangan aplikasi PHP atau Laravel yang menggunakan MVC (Model-View-Controller) dan ORM (Object-Relational Mapping) maka beberapa programmer telah terbiasa dengan satu Model raksasa. Misalnya sebuah class OrderModel bisa digunakan untuk menyimpan pesanan baru yang kompleks (misalnya memvalidasi stok / menghitung pajak), dan sekaligus digunakan untuk menampilkan ribuan baris laporan pesanan di halaman admin. Pada skala kecil, ini sangat efisien. Namun di skala besar (Enterprise), ini memicu masalah yang dikenal dengan nama "Fat Model". Model seperti ini menjadi sangat berat, memakan banyak memori, dan sulit dioptimasi. Query read yang seharusnya cepat menjadi lambat karena harus memuat logika validasi bisnis yang sebenarnya hanya dibutuhkan saat menyimpan data (write).
Solusinya? Kita pisahkan model tersebut menjadi dua bagian yang benar-benar terpisah menggunakan pola arsitektur CQRS (Command Query Responsibility Segregation).
Daftar Isi
- Apa itu CQRS? Evolusi dari Master-Slave
- Arsitektur CQRS
- Implementasi PHP: Command (Tulis)
- Implementasi PHP: Query (Baca)
- Antarmuka Pengguna (UI) Berbasis Tugas
- Kesimpulan: Kapan Anda Harus Memakainya?
1. Apa itu CQRS?
Pada artikel sebelumnya, kita telah membahas topologi Master-Slave MySQL, di mana server database dipecah menjadi Master (untuk Tulis) dan Slave (untuk Baca).
CQRS adalah pengembangan dari konsep tersebut tetapi berada di level Application Architecture.
Prinsip utama CQRS sangat sederhana:
- Command (Perintah) adalah objek yang tugasnya untuk mengubah state (menulis) data. Ia mengeksekusi logika bisnis yang rumit, namun tidak pernah mengembalikan data (kecuali status sukses/gagal).
- Query (Pertanyaan) adalah objek yang tugasnya untuk mengembalikan data (membaca). Ia tidak pernah mengubah data apa pun di database.
Dengan pemisahan ini, model Command bisa dihubungkan ke Master DB, dan model Query bisa dihubungkan ke Slave DB. Pada artikel ini, kita hanya akan membahas implementasi CQRS tanpa membahas Event Sourcing. Untuk lebih memahami CQRS dengan Event Sourcing, silakan baca artikel ini.
2. Arsitektur CQRS
Perhatikan diagram di bawah ini, pemisahan tanggung jawab terlihat jelas, dari Client hingga ke Database.

Jalur COMMAND PATH (Merah) berjalan sinkron, melakukan validasi ketat, lalu menyimpan ke Write DB. Setelah itu, Event Bus atau Replication Log secara asinkron (di latar belakang) menyalin data tersebut ke Read DB.
Jalur QUERY PATH (Biru) sangat sederhana dan cepat. Ia menuju langsung ke Read DB tanpa melewati lapisan validasi bisnis yang biasanya bertingkat dan cukup membebani.
Untuk melengkapi pemahaman tentang arsitektur ini, saya akan menuliskan implementasinya dalam PHP.
3. Implementasi PHP: Command (Tulis)
Mari kita implementasikan arsitektur ini dalam kode PHP. Kita menggunakan pola Data Transfer Object (DTO) dan Handler.
Pertama, buat objek Command.
<?php
// 1. DTO: Hanya menampung data yang ingin diubah
class CreateOrderCommand {
public string $customerId;
public array $items;
public float $totalAmount;
public function __construct(string $customerId, array $items, float $totalAmount) {
$this->customerId = $customerId;
$this->items = $items;
$this->totalAmount = $totalAmount;
}
}
Kedua, buat Command Handler. Di sinilah logika bisnis kompleks dieksekusi. Handler ini harus terkoneksi ke Master Database.
<?php
// 2. Handler: Mengeksekusi logika dan menyimpan ke Master DB
class CreateOrderHandler {
private PDO $masterDb;
private InventoryService $inventory;
public function __construct(PDO $masterDb, InventoryService $inventory) {
$this->masterDb = $masterDb;
$this->inventory = $inventory;
}
public function handle(CreateOrderCommand $command): void {
// Logika Bisnis Kompleks: Validasi stok barang
if (!$this->inventory->checkAvailability($command->items)) {
throw new Exception("Stok tidak mencukupi.");
}
// Tulis ke Master DB (contoh PDO)
$stmt = $this->masterDb->prepare("INSERT INTO orders (customer_id, total, status) VALUES (?, ?, 'PENDING')");
$stmt->execute([$command->customerId, $command->totalAmount]);
// Catatan: Tidak ada "return $orderData" di sini.
// Command murni hanya melakukan mutasi data.
}
}
4. Implementasi PHP: Query (Baca)
Sekarang mari kita buat jalur untuk membaca laporan transaksi. Kita tidak menggunakan Handler yang sama, apalagi model yang sama. Kita buat model terpisah yang terhubung langsung ke Slave DB.
<?php
// 1. DTO Query: Kriteria pencarian
class GetRecentOrdersQuery {
public int $limit;
public function __construct(int $limit = 10) {
$this->limit = $limit;
}
}
// 2. Handler: Langsung hajar Read DB (Slave) tanpa logika bisnis
class GetRecentOrdersHandler {
private PDO $slaveDb;
public function __construct(PDO $slaveDb) {
$this->slaveDb = $slaveDb;
}
public function handle(GetRecentOrdersQuery $query): array {
// Query membaca langsung, sangat cepat, tanpa instansiasi objek Model ORM yang lambat
$stmt = $this->slaveDb->prepare("
SELECT id, customer_name, total, created_at
FROM orders_read_model
ORDER BY created_at DESC
LIMIT ?
");
$stmt->execute([$query->limit]);
// Langsung kembalikan array murni
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
[CATATAN] Karena Read Model sepenuhnya terpisah, struktur tabel di Slave DB bisa saja berbeda dengan Master DB! Anda bisa melakukan denormalisasi (menggabungkan data JOIN ke dalam satu flat table) di Read DB agar kecepatan bacanya meningkat secara eksponensial.
5. Antarmuka Pengguna (UI)
CQRS tidak hanya mengubah backend, tapi idealnya juga mengubah frontend. Aplikasi CRUD klasik biasanya memiliki satu halaman form yang besar. Namun di CQRS, UI dirancang berorientasi pada tugas tertentu (Task-Based UI). Perhatikan desain dashboard SaaS di bawah ini.

Di latar belakang, Anda melihat Read Model (Tabel Data Transaksi). Ketika pengguna mengklik tombol "Refund", sistem tidak memuat Edit Form terlebih dahulu, melainkan langsung memunculkan panel Command Action (Proses Refund).
Panel ini mengirimkan satu perintah spesifik (ProcessRefundCommand) ke backend. Secara spesifik, dan memicu mutasi data tanpa mempedulikan cara merender tabel laporannya kembali.
6. Kapan Anda Harus Memakainya?
Dengan CQRS, tim pengembang Anda bisa mengerjakan optimasi Query tanpa khawatir merusak logic Command, begitu pula sebaliknya.
Namun, ada harga yang harus dibayar yaitu
- Eventual Consistency: Sama seperti Master-Slave, ada kemungkinan lag (jeda waktu). Ketika Command berhasil (misal: "Barang A terjual"), mungkin butuh waktu 1-2 detik sebelum Query (Laporan Stok) menunjukkan angka yang sinkron. Anda harus mendesain UI yang ramah untuk kondisi ini.
- Duplikasi Kode: Anda kini memiliki lebih banyak file class (satu fitur butuh DTO Command, Handler Command, DTO Query, dan Handler Query).
Gunakan CQRS secara parsial. Anda tidak perlu menggunakan CQRS untuk seluruh aplikasi Anda. Gunakanlah pola arsitektur ini HANYA pada modul bisnis Anda yang paling berat dan krusial—misalnya, sistem checkout/cart atau kalkulasi ledger keuangan. Biarkan sisa fitur sekunder Anda tetap menggunakan CRUD sederhana demi efisiensi waktu pengembangan.