Pemisahan Tanggung Jawab Kueri Perintah (CQRS) adalah pola desain yang memisahkan operasi baca dan tulis untuk penyimpanan data ke dalam model data terpisah. Ini memungkinkan setiap model dioptimalkan secara independen dan dapat meningkatkan performa, skalabilitas, dan keamanan aplikasi.
Dalam arsitektur tradisional, model data tunggal sering digunakan untuk operasi baca dan tulis. Pendekatan ini mudah dan berfungsi dengan baik untuk operasi CRUD dasar (lihat gambar 1).
Gambar 1. Arsitektur CRUD tradisional.
Namun, ketika aplikasi tumbuh, mengoptimalkan operasi baca dan tulis pada satu model data menjadi semakin menantang. Operasi baca dan tulis sering kali memiliki performa dan kebutuhan penskalakan yang berbeda. Arsitektur CRUD tradisional tidak memperhitungkan asimetri ini. Ini menyebabkan beberapa tantangan:
Ketidakcocokan Data: Representasi baca dan tulis data sering berbeda. Beberapa bidang yang diperlukan selama pembaruan mungkin tidak perlu selama pembacaan.
Mengunci ketidakcocokan: Operasi paralel pada himpunan data yang sama dapat menyebabkan ketidakcocokan kunci.
Masalah performa: Pendekatan tradisional dapat memiliki efek negatif pada performa karena beban pada penyimpanan data dan lapisan akses data, dan kompleksitas kueri yang diperlukan untuk mengambil informasi.
Masalah keamanan: Mengelola keamanan menjadi sulit ketika entitas tunduk pada operasi baca dan tulis. Tumpang tindih ini dapat mengekspos data dalam konteks yang tidak diinginkan.
Menggabungkan tanggung jawab ini dapat mengakibatkan model yang terlalu rumit yang mencoba melakukan terlalu banyak hal.
Gunakan pola CQRS untuk memisahkan operasi tulis (perintah) dari operasi baca ( kueri). Perintah bertanggung jawab untuk memperbarui data. Kueri bertanggung jawab untuk mengambil data.
Memahami perintah. Perintah harus mewakili tugas bisnis tertentu daripada pembaruan data tingkat rendah. Misalnya, di aplikasi pemesanan hotel, gunakan "Pesan kamar hotel" alih-alih "Atur ReservationStatus ke Dipesan." Pendekatan ini lebih mencerminkan niat di balik tindakan pengguna dan menyelaraskan perintah dengan proses bisnis. Untuk memastikan perintah berhasil, Anda mungkin perlu menyempurnakan alur interaksi pengguna, logika sisi server, dan mempertimbangkan pemrosesan asinkron.
Area penyempurnaan | Rekomendasi |
---|---|
Validasi sisi klien | Validasi kondisi tertentu sebelum mengirim perintah untuk mencegah kegagalan yang jelas. Misalnya, jika tidak ada kamar yang tersedia, nonaktifkan tombol "Pesan" dan berikan pesan yang jelas dan mudah digunakan di UI yang menjelaskan mengapa pemesanan tidak dimungkinkan. Penyiapan ini mengurangi permintaan server yang tidak perlu dan memberikan umpan balik langsung kepada pengguna, meningkatkan pengalaman mereka. |
Logika sisi server | Tingkatkan logika bisnis untuk menangani kasus tepi dan kegagalan dengan anggun. Misalnya, untuk mengatasi kondisi balapan (beberapa pengguna mencoba memesan ruang terakhir yang tersedia), pertimbangkan untuk menambahkan pengguna ke daftar tunggu atau menyarankan opsi alternatif. |
Pemrosesan asinkron | Anda juga dapat perintah proses secara asinkron dengan menempatkannya pada antrean, daripada menanganinya secara sinkron. |
Memahami kueri. Kueri tidak pernah mengubah data. Sebaliknya, mereka mengembalikan Objek Transfer Data (DTO) yang menyajikan data yang diperlukan dalam format yang nyaman, tanpa logika domain apa pun. Pemisahan kekhawatiran yang jelas ini menyederhanakan desain dan implementasi sistem.
Memisahkan model baca dari model tulis menyederhanakan desain dan implementasi sistem dengan mengatasi kekhawatiran yang berbeda untuk penulisan dan bacaan data. Pemisahan ini meningkatkan kejelasan, skalabilitas, dan performa tetapi memperkenalkan beberapa trade-off. Misalnya, alat perancah seperti kerangka kerja O/RM tidak dapat secara otomatis menghasilkan kode CQRS dari skema database, yang memerlukan logika kustom untuk menjembatani kesenjangan.
Bagian berikut mengeksplorasi dua pendekatan utama untuk menerapkan pemisahan model baca dan tulis di CQRS. Setiap pendekatan dilengkapi dengan manfaat dan tantangan unik, seperti sinkronisasi dan manajemen konsistensi.
Pendekatan ini mewakili tingkat dasar CQRS, di mana model baca dan tulis berbagi database yang mendasar tunggal tetapi mempertahankan logika yang berbeda untuk operasi mereka. Dengan menentukan kekhawatiran terpisah, strategi ini meningkatkan kesederhanaan sambil memberikan manfaat dalam skalabilitas dan performa untuk kasus penggunaan umum. Arsitektur CQRS dasar memungkinkan Anda untuk menggambarkan model tulis dari model baca sambil mengandalkan penyimpanan data bersama (melihat gambar 2).
Gambar 2. Arsitektur CQRS dasar dengan satu penyimpanan data.
Pendekatan ini meningkatkan kejelasan, performa, dan skalabilitas dengan mendefinisikan model yang berbeda untuk menangani masalah tulis dan baca:
Menulis model: Dirancang untuk menangani perintah yang memperbarui atau mempertahankan data. Ini termasuk validasi, logika domain, dan memastikan konsistensi data dengan mengoptimalkan integritas transaksi dan proses bisnis.
Model baca: Dirancang untuk melayani kueri untuk mengambil data. Ini berfokus pada pembuatan DTO (objek transfer data) atau proyeksi yang dioptimalkan untuk lapisan presentasi. Ini meningkatkan performa dan responsivitas kueri dengan menghindari logika domain.
Implementasi CQRS yang lebih canggih menggunakan penyimpanan data yang berbeda untuk model baca dan tulis. Pemisahan penyimpanan data baca dan tulis memungkinkan Anda menskalakan masing-masing agar sesuai dengan beban. Ini juga memungkinkan Anda menggunakan teknologi penyimpanan yang berbeda untuk setiap penyimpanan data. Anda bisa menggunakan database dokumen untuk penyimpanan data baca dan database relasional untuk penyimpanan data tulis (lihat gambar 3).
Gambar 3. Arsitektur CQRS dengan penyimpanan data baca dan tulis terpisah.
Menyinkronkan penyimpanan data terpisah: Saat menggunakan penyimpanan terpisah, Anda harus memastikan keduanya tetap sinkron. Pola umumnya adalah membuat model tulis menerbitkan peristiwa setiap kali memperbarui database, yang digunakan model baca untuk me-refresh datanya. Untuk informasi selengkapnya tentang menggunakan peristiwa, lihat gaya arsitektur berbasis peristiwa. Namun, Anda biasanya tidak dapat mendaftarkan broker pesan dan database ke dalam satu transaksi terdistribusi. Jadi, mungkin ada tantangan dalam menjamin konsistensi saat memperbarui database dan menerbitkan peristiwa. Untuk informasi selengkapnya, lihat pemrosesan pesan idempoen.
Membaca penyimpanan data: Penyimpanan data baca dapat menggunakan skema datanya sendiri yang dioptimalkan untuk kueri. Misalnya, data dapat menyimpan tampilan terwujud data untuk menghindari gabungan kompleks atau pemetaan O/RM. Penyimpanan baca dapat menjadi replika baca-saja dari penyimpanan tulis atau memiliki struktur yang berbeda. Menyebarkan beberapa replika baca-saja dapat meningkatkan performa dengan mengurangi latensi dan meningkatkan ketersediaan, terutama dalam skenario terdistribusi.
Penyekalaan independen. CQRS memungkinkan model baca dan tulis untuk menskalakan secara independen, yang dapat membantu meminimalkan ketidakcocokan kunci dan meningkatkan performa sistem di bawah beban.
Skema data yang dioptimalkan. Operasi baca dapat menggunakan skema yang dioptimalkan untuk kueri. Operasi tulis menggunakan skema yang dioptimalkan untuk pembaruan.
Keamanan. Dengan memisahkan baca dan tulis, Anda dapat memastikan bahwa hanya entitas atau operasi domain yang sesuai yang memiliki izin untuk melakukan tindakan tulis pada data.
Pemisahan masalah. Memisahkan tanggung jawab baca dan tulis menghasilkan model yang lebih bersih dan lebih dapat dipertahankan. Sisi tulis biasanya menangani logika bisnis yang kompleks, sementara sisi baca dapat tetap sederhana dan berfokus pada efisiensi kueri.
Kueri yang lebih sederhana. Saat Anda menyimpan tampilan materialisasi dalam database baca, aplikasi dapat menghindari gabungan kompleks saat mengkueri.
Beberapa tantangan dalam menerapkan pola ini antara lain:
Peningkatan kompleksitas. Meskipun konsep inti CQRS mudah, itu dapat memperkenalkan kompleksitas yang signifikan ke dalam desain aplikasi, terutama ketika dikombinasikan dengan pola Sumber Peristiwa.
tantangan Olahpesan. Meskipun olahpesan bukan persyaratan untuk CQRS, Anda sering menggunakannya untuk memproses perintah dan menerbitkan peristiwa pembaruan. Saat olahpesan terlibat, sistem harus memperhitungkan potensi masalah seperti kegagalan pesan, duplikat, dan percobaan ulang. Lihat panduan tentang Antrean Prioritas
untuk strategi menangani perintah dengan berbagai prioritas. Konsistensi akhirnya. Ketika database baca dan tulis dipisahkan, data baca mungkin tidak segera mencerminkan perubahan terbaru, yang mengarah ke data kedaluarsa. Memastikan penyimpanan model baca tetap up-to-date dengan perubahan di penyimpanan model tulis bisa menjadi tantangan. Selain itu, mendeteksi dan menangani skenario di mana pengguna bertindak pada data kedaluarsa memerlukan pertimbangan yang cermat.
Pola CQRS berguna dalam skenario yang memerlukan pemisahan yang jelas antara modifikasi data (perintah) dan kueri data (baca). Pertimbangkan untuk menggunakan CQRS dalam situasi berikut:
Domain kolaboratif: Di lingkungan tempat beberapa pengguna mengakses dan memodifikasi data yang sama secara bersamaan, CQRS membantu mengurangi konflik penggabungan. Perintah dapat mencakup granularitas yang cukup untuk mencegah konflik, dan sistem dapat menyelesaikan apa pun yang muncul dalam logika perintah.
Antarmuka pengguna berbasis Tugas: Aplikasi yang memandu pengguna melalui proses kompleks sebagai serangkaian langkah atau dengan model domain kompleks mendapat manfaat dari CQRS.
Model tulis memiliki tumpukan pemrosesan perintah lengkap dengan logika bisnis, validasi input, dan validasi bisnis. Model tulis mungkin memperlakukan sekumpulan objek terkait sebagai satu unit untuk perubahan data, tahu sebagai agregat
dalam terminologi desain berbasis domain. Model tulis mungkin juga memastikan bahwa objek ini selalu dalam keadaan konsisten. Model baca tidak memiliki logika bisnis atau tumpukan validasi. Ini mengembalikan DTO untuk digunakan dalam model tampilan. Model baca akhirnya konsisten dengan model tulis.
Penyetelan performa: Sistem di mana performa pembacaan data harus disempurnakan secara terpisah dari performa penulisan data, terutama ketika jumlah bacaan lebih besar dari jumlah penulisan, manfaat dari CQRS. Model baca diskalakan secara horizontal untuk menangani volume kueri besar, sementara model tulis berjalan pada lebih sedikit instans untuk meminimalkan konflik penggabungan dan mempertahankan konsistensi.
Pemisahan masalah pengembangan: CQRS memungkinkan tim untuk bekerja secara independen. Satu tim berfokus pada penerapan logika bisnis yang kompleks dalam model tulis, sementara tim lain mengembangkan model baca dan komponen antarmuka pengguna.
sistem yang berkembang: CQRS mendukung sistem yang berkembang dari waktu ke waktu. Ini mengakomodasi versi model baru, perubahan yang sering pada aturan bisnis, atau modifikasi lainnya tanpa memengaruhi fungsionalitas yang ada.
Integrasi sistem: Sistem yang terintegrasi dengan subsistem lain, terutama yang menggunakan Event Sourcing, tetap tersedia bahkan jika subsistem gagal sementara. CQRS mengisolasi kegagalan, mencegah satu komponen memengaruhi seluruh sistem.
Hindari CQRS dalam situasi berikut:
Domain atau aturan bisnisnya sederhana.
Antarmuka pengguna gaya CRUD sederhana dan operasi akses data sudah cukup.
Arsitek harus mengevaluasi cara menggunakan pola CQRS dalam desain beban kerja mereka untuk mengatasi tujuan dan prinsip yang tercakup dalam pilar Azure Well-Architected Framework . Contohnya:
Pilar | Bagaimana pola ini mendukung tujuan pilar |
---|---|
Efisiensi Performa membantu beban kerja Anda memenuhi tuntutan secara efisien melalui pengoptimalan dalam penskalaan, data, kode. | Pemisahan operasi baca dan tulis dalam beban kerja baca-ke-tulis yang tinggi memungkinkan performa yang ditargetkan dan pengoptimalan penskalaan untuk tujuan spesifik setiap operasi. - PE:05 Penskalaan dan pemartisian - Performa DATA PE:08 |
Seperti halnya keputusan desain apa pun, pertimbangkan tradeoff terhadap tujuan pilar lain yang mungkin diperkenalkan dengan pola ini.
Beberapa implementasi CQRS menggabungkan pola Event Sourcing, yang menyimpan status sistem sebagai serangkaian peristiwa kronologis. Setiap peristiwa menangkap perubahan yang dilakukan pada data pada waktu tertentu. Untuk menentukan status saat ini, sistem memutar ulang peristiwa ini secara berurutan. Dalam kombinasi ini:
Penyimpanan peristiwa adalah model penulisan
dan satu sumber kebenaran. Model baca menghasilkan tampilan terwujud dari peristiwa ini, biasanya dalam bentuk yang sangat denormalisasi. Tampilan ini mengoptimalkan pengambilan data dengan menyesuaikan struktur untuk mengkueri dan menampilkan persyaratan.
Peristiwa yang sama yang memperbarui model tulis dapat berfungsi sebagai input ke model baca. Model baca kemudian dapat membangun rekam jepret real-time dari status saat ini. Rekam jepret ini mengoptimalkan kueri dengan memberikan tampilan data yang efisien dan telah dikomputasi sebelumnya.
Alih-alih menyimpan status saat ini secara langsung, sistem menggunakan aliran peristiwa sebagai penyimpanan tulis. Pendekatan ini mengurangi konflik pembaruan pada agregat dan meningkatkan performa dan skalabilitas. Sistem dapat memproses peristiwa ini secara asinkron untuk membangun atau memperbarui tampilan materialisasi untuk penyimpanan baca.
Karena penyimpanan peristiwa bertindak sebagai sumber kebenaran tunggal, Anda dapat dengan mudah meregenerasi tampilan materialisasi atau beradaptasi dengan perubahan dalam model baca dengan memutar ulang peristiwa historis. Intinya, tampilan materialisasi berfungsi sebagai cache baca-saja yang tahan lama yang dioptimalkan untuk kueri yang cepat dan efisien.
Sebelum Anda menggabungkan pola CQRS dengan pola Sumber Peristiwa, evaluasi pertimbangan berikut:
Konsistensi akhir: Karena penyimpanan tulis dan baca terpisah, pembaruan pada penyimpanan baca mungkin tertinggal dari pembuatan peristiwa, menghasilkan konsistensi akhirnya.
Peningkatan kompleksitas: Menggabungkan CQRS dengan Event Sourcing memerlukan pendekatan desain yang berbeda, yang dapat membuat implementasi yang sukses lebih menantang. Anda harus menulis kode untuk menghasilkan, memproses, dan menangani peristiwa, dan merakit atau memperbarui tampilan untuk model baca. Namun, Event Sourcing menyederhanakan pemodelan domain dan memungkinkan Anda membangun kembali atau membuat tampilan baru dengan mudah dengan mempertahankan riwayat dan niat semua perubahan data.
Performa pembuatan tampilan: Menghasilkan tampilan terwujud untuk model baca dapat menghabiskan waktu dan sumber daya yang signifikan. Hal yang sama berlaku untuk memproyeksikan data dengan memutar ulang dan memproses peristiwa untuk entitas atau koleksi tertentu. Efek ini meningkat ketika perhitungan melibatkan analisis atau penjumlahan nilai selama jangka panjang, karena semua peristiwa terkait harus diperiksa. Terapkan rekam jepret data secara berkala. Misalnya, simpan rekam jepret berkala dari total agregat (berapa kali tindakan tertentu terjadi) atau status entitas saat ini. Rekam jepret mengurangi kebutuhan untuk memproses riwayat peristiwa lengkap berulang kali, meningkatkan performa.
Kode berikut menunjukkan beberapa ekstrak dari contoh penerapan CQRS yang menggunakan definisi berbeda untuk model baca dan tulis. Antarmuka model tidak menentukan fitur apa pun dari penyimpanan data yang mendasarinya, dan dapat berkembang dan disesuaikan secara independen karena antarmuka ini dipisahkan.
Kode berikut menunjukkan definisi model baca.
// Query interface
namespace ReadModel
{
public interface ProductsDao
{
ProductDisplay FindById(int productId);
ICollection<ProductDisplay> FindByName(string name);
ICollection<ProductInventory> FindOutOfStockProducts();
ICollection<ProductDisplay> FindRelatedProducts(int productId);
}
public class ProductDisplay
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal UnitPrice { get; set; }
public bool IsOutOfStock { get; set; }
public double UserRating { get; set; }
}
public class ProductInventory
{
public int Id { get; set; }
public string Name { get; set; }
public int CurrentStock { get; set; }
}
}
Sistem ini memungkinkan pengguna untuk menilai produk. Kode aplikasi melakukannya menggunakan perintah RateProduct
yang ditunjukkan dalam kode berikut.
public interface ICommand
{
Guid Id { get; }
}
public class RateProduct : ICommand
{
public RateProduct()
{
this.Id = Guid.NewGuid();
}
public Guid Id { get; set; }
public int ProductId { get; set; }
public int Rating { get; set; }
public int UserId {get; set; }
}
Sistem menggunakan kelas ProductsCommandHandler
untuk menangani perintah yang dikirim oleh aplikasi. Klien biasanya mengirim perintah ke domain melalui sistem olahpesan seperti antrean. Penangan perintah menerima perintah ini dan memanggil metode antarmuka domain. Granularitas setiap perintah didesain untuk mengurangi kemungkinan permintaan yang bertentangan. Kode berikut menunjukkan kerangka kelas ProductsCommandHandler
.
public class ProductsCommandHandler :
ICommandHandler<AddNewProduct>,
ICommandHandler<RateProduct>,
ICommandHandler<AddToInventory>,
ICommandHandler<ConfirmItemShipped>,
ICommandHandler<UpdateStockFromInventoryRecount>
{
private readonly IRepository<Product> repository;
public ProductsCommandHandler (IRepository<Product> repository)
{
this.repository = repository;
}
void Handle (AddNewProduct command)
{
...
}
void Handle (RateProduct command)
{
var product = repository.Find(command.ProductId);
if (product != null)
{
product.RateProduct(command.UserId, command.Rating);
repository.Save(product);
}
}
void Handle (AddToInventory command)
{
...
}
void Handle (ConfirmItemsShipped command)
{
...
}
void Handle (UpdateStockFromInventoryRecount command)
{
...
}
}
Pola dan panduan berikut berguna saat menerapkan pola ini:
- Partisi data horizontal, vertikal, dan fungsional. Menjelaskan praktik terbaik untuk membagi data ke dalam partisi yang dapat dikelola dan diakses secara terpisah untuk meningkatkan skalabilitas, mengurangi pertentangan, dan mengoptimalkan performa.
Pola Sumber Peristiwa. Menjelaskan cara menggunakan Event Sourcing dengan pola CQRS. Ini menunjukkan kepada Anda cara menyederhanakan tugas di domain yang kompleks sambil meningkatkan performa, skalabilitas, dan responsivitas. Ini juga menjelaskan cara memberikan konsistensi untuk data transaksional sambil mempertahankan jejak audit penuh dan riwayat yang dapat memungkinkan tindakan kompensasi.
Pola Tampilan Terwujud. Model baca dari penerapan CQRS dapat berisi tampilan material dari data model tulis, atau model baca dapat digunakan untuk menghasilkan tampilan material.