CQRS Pattern: Memahami Command Query Responsibility Segregation
Daftar Isi
- Pendahuluan
- Apa Itu CQRS?
- Mengapa Memisahkan Read dan Write?
- Arsitektur dan Alur Kerja CQRS
- Strategi Sinkronisasi Data
- Hubungan CQRS dengan Event Sourcing
- Kapan Harus Menggunakan CQRS?
- FAQ
Pendahuluan
Saat kita pertama kali belajar membangun aplikasi web, pola yang paling sering kita gunakan adalah CRUD (Create, Read, Update, Delete). Dalam arsitektur CRUD tradisional, kita menggunakan model data yang sama untuk melakukan operasi tulis (tambah, edit, hapus) maupun operasi baca (tampilkan data).
Pendekatan ini sangat bagus untuk aplikasi skala kecil hingga menengah karena sederhana dan cepat diimplementasikan. Namun, seiring bertambahnya beban traffic dan kompleksitas bisnis, model data tunggal ini mulai menunjukkan kelemahannya. Kueri baca yang kompleks (seperti laporan statistik atau pencarian multi-filter) membutuhkan struktur data ter-denormalisasi agar cepat, sementara operasi tulis membutuhkan struktur data yang ternormalisasi untuk menjaga integritas data.
Untuk mengatasi bentrokan kebutuhan ini, hadirlah pola arsitektur CQRS (Command Query Responsibility Segregation). Pola ini memisahkan tanggung jawab penulisan data dan pembacaan data ke dalam jalur yang berbeda.
Apa Itu CQRS?
CQRS diperkenalkan pertama kali oleh Greg Young berdasarkan prinsip Command Query Separation (CQS) yang dirumuskan oleh Bertrand Meyer. Ide intinya sangat lugas: Pisahkan operasi yang memodifikasi data dari operasi yang hanya membaca data.
Secara praktis, CQRS membagi fungsionalitas sistem menjadi dua bagian:
-
Command (Sisi Tulis / Write Model)
- Bertanggung jawab untuk melakukan perubahan state pada sistem (Create, Update, Delete).
- Fokus pada validasi aturan bisnis (business rules) dan menjaga integritas data.
- Idealnya, Command tidak mengembalikan data apa pun ke klien, kecuali status keberhasilan (atau metadata minimal seperti ID objek yang baru dibuat).
-
Query (Sisi Baca / Read Model)
- Bertanggung jawab untuk mengambil data dari sistem untuk ditampilkan kepada pengguna.
- Bersifat side-effect free—menjalankan query tidak boleh mengubah state data sama sekali.
- Fokus pada kecepatan respons dan kemudahan penyajian data dalam format DTO (Data Transfer Object) yang siap pakai oleh UI.
Mengapa Memisahkan Read dan Write?
Pemisahan ini mungkin terkesan menambah beban kerja ekstra, namun memberikan beberapa keuntungan luar biasa untuk sistem skala besar:
- Optimasi Independen: Kamu bisa mendesain database tulis secara ternormalisasi (3NF) untuk mempercepat proses write dan transaksi. Di sisi lain, database baca dapat didesain secara denormalisasi (flat table) agar kueri baca tidak memerlukan JOIN yang rumit dan berat.
- Skalabilitas yang Fleksibel: Pada sebagian besar aplikasi web, jumlah pembacaan data (read ratio) jauh lebih tinggi daripada penulisan data (write ratio)—bisa mencapai 10:1 atau bahkan 100:1. Dengan CQRS, kamu bisa melakukan scale out pada server query secara independen tanpa perlu memperbesar kapasitas database tulis.
- Penyederhanaan Logika Bisnis: Karena sisi Query tidak melakukan validasi bisnis dan sisi Command tidak memikirkan format tampilan UI, kode program menjadi lebih terfokus, bersih, dan mudah dirawat.
- Keamanan yang Lebih Baik: Kamu bisa menerapkan kebijakan keamanan yang berbeda. Misalnya, memberikan izin akses tulis (write permission) hanya kepada service internal tertentu, sementara klien luar hanya diberi akses kueri baca (read-only).
Arsitektur dan Alur Kerja CQRS
Berikut adalah gambaran bagaimana data mengalir di dalam sistem yang menerapkan pola CQRS:
graph TD
Client[Client / UI] -->|1. Kirim Command / Tulis| Controller[Controller / API]
Client -->|4. Kirim Query / Baca| Controller
subgraph Sisi Command - Write Model
Controller -->|Command| CmdHandler[Command Handler]
CmdHandler -->|Update State| WriteDB[(Write Database)]
end
subgraph Sisi Query - Read Model
Controller -->|Query| QryHandler[Query Handler]
QryHandler -->|Fetch Data| ReadDB[(Read Database)]
ReadDB -->|Return DTO| QryHandler
end
WriteDB -->|2. Event / Sync| SyncProcess[Synchronization / Message Broker]
SyncProcess -->|3. Proyeksikan Data| ReadDB
QryHandler -.->|5. Return Data| Client
Penjelasan Alur Kerja:
- Command Flow: Pengguna mengirimkan aksi (misalnya, mendaftarkan akun baru). Controller menerimanya sebagai sebuah Command dan meneruskannya ke Command Handler. Setelah lolos validasi bisnis, data disimpan ke Write Database.
- Synchronization: Setelah database tulis diperbarui, sebuah event pembaruan dikirim (bisa melalui Message Broker seperti Kafka atau RabbitMQ).
- Projection: Proses background menangkap event tersebut dan memperbarui Read Database agar sinkron dengan data terbaru.
- Query Flow: Ketika pengguna lain ingin melihat daftar pengguna, permintaan masuk sebagai Query. Controller meneruskannya ke Query Handler yang langsung mengambil data dari Read Database tanpa melalui logika bisnis yang rumit.
Strategi Sinkronisasi Data
Tantangan terbesar dalam penerapan CQRS adalah bagaimana menjaga agar data di database baca tetap sama dengan database tulis. Ada dua pendekatan umum yang bisa digunakan:
1. Sinkronisasi Sinkron (Synchronous Projection)
Dalam pendekatan ini, Command Handler bertanggung jawab untuk memperbarui database tulis sekaligus database baca dalam satu transaksi yang sama (atomic transaction).
- Kelebihan: Data selalu konsisten secara instan (strong consistency). Begitu transaksi tulis sukses, pengguna langsung bisa melihat data barunya.
- Kekurangan: Proses penulisan menjadi lebih lambat karena harus memperbarui dua tempat sekaligus. Jika database baca sedang down, proses tulis juga akan ikut gagal.
2. Sinkronisasi Asinkron (Eventual Consistency)
Pembaruan database baca dilakukan di latar belakang (background process) menggunakan arsitektur berbasis event (event-driven). Setelah write database sukses, sistem mengirimkan event, lalu consumer akan memproses event tersebut untuk memperbarui read database secara asinkron.
- Kelebihan: Peforma penulisan sangat cepat dan tidak terikat oleh database baca. Sistem tetap bisa menerima input tulis meskipun database baca sedang mengalami gangguan.
- Kekurangan: Terjadinya Eventual Consistency. Klien mungkin mengalami jeda waktu singkat (milidetik hingga beberapa detik) di mana data baru belum langsung muncul di database baca sesaat setelah mereka menuliskannya.
Hubungan CQRS dengan Event Sourcing
CQRS sering kali dibahas bersamaan dengan Event Sourcing, namun perlu dicatat bahwa keduanya adalah pola arsitektur yang terpisah. Kamu bisa menerapkan CQRS tanpa Event Sourcing, begitu pula sebaliknya. Namun, keduanya memang sangat serasi saat digabungkan.
- Event Sourcing adalah pola di mana kita tidak menyimpan state akhir dari suatu data, melainkan menyimpan seluruh riwayat perubahan data tersebut sebagai urutan event yang append-only (tidak bisa diubah).
- Karena membaca data dari riwayat event (Event Sourcing) secara langsung sangatlah lambat, CQRS digunakan untuk membuat proyeksi (read model) yang siap saji dari riwayat event tersebut ke dalam database baca yang dioptimalkan untuk kueri.
Kapan Harus Menggunakan CQRS?
Meskipun CQRS menawarkan banyak keuntungan, pola ini memperkenalkan kompleksitas arsitektur yang cukup tinggi. Oleh karena itu, jangan menggunakannya secara sembarangan.
CQRS sangat direkomendasikan jika:
- Tingginya perbedaan rasio Read dan Write: Aplikasi kamu melayani jutaan pembacaan data, namun penulisan datanya tergolong jarang (misalnya portal berita, e-commerce, atau media sosial).
- Domain Bisnis yang Kompleks: Logika bisnis untuk melakukan validasi transaksi sangat rumit, sementara kueri bacanya membutuhkan penggabungan data dari berbagai tempat.
- Kebutuhan Performa Pencarian Tinggi: Kamu ingin menggunakan teknologi database berbeda, seperti menyimpan data utama di PostgreSQL (tulis) dan melakukan kueri pencarian melalui Elasticsearch (baca).
Hindari CQRS jika:
- Aplikasi bersifat CRUD sederhana: Jika aplikasimu hanya melakukan operasi input-output data sederhana tanpa logika bisnis yang rumit.
- Membutuhkan Konsistensi Instan Mutlak: Jika sistem sama sekali tidak bisa mentoleransi eventual consistency (misalnya sistem transfer dana perbankan inti, meskipun ini pun bisa disiasati dengan desain yang matap).
- Keterbatasan Resource Tim: Mengelola arsitektur CQRS membutuhkan pemahaman tentang distributed system, eventual consistency, dan debugging asinkron yang matang.
FAQ
Apakah database tulis dan database baca harus menggunakan teknologi yang berbeda?
Tidak harus. Pada implementasi paling sederhana, kamu bisa menggunakan satu database fisik yang sama (misalnya PostgreSQL), tetapi memisahkannya pada level kode program dengan membedakan kelas model data tulis dan model data baca.
Bagaimana cara mengatasi Eventual Consistency di sisi UI?
Kamu bisa menggunakan teknik optimisme UI (optimistic UI updates) di mana frontend langsung menampilkan data baru secara lokal sebelum server mengonfirmasi sinkronisasi selesai, atau menggunakan spinner pemuatan sementara, atau merancang alur kerja pengguna agar tidak langsung diarahkan ke halaman daftar setelah melakukan perubahan data.
Apakah semua bagian aplikasi harus menerapkan CQRS?
Tidak. Kamu bisa menerapkan CQRS hanya pada modul-modul tertentu yang memiliki beban kueri sangat tinggi atau logika bisnis yang rumit (misal modul keranjang belanja atau laporan bulanan), sedangkan modul lainnya tetap menggunakan pola CRUD biasa.
Bagaimana pendapatmu tentang pola CQRS ini? Apakah kompleksitas yang dihadirkannya sebanding dengan performa yang didapatkan? Jika kamu pernah menerapkannya di tempat kerja, bagikan ceritamu di kolom komentar!