Bagikan melalui


Pola CQRS

Pemisahan Tanggung Jawab Kueri Perintah (CQRS) adalah pola desain yang memisahkan operasi baca dan tulis untuk penyimpanan data ke dalam model data terpisah. Pendekatan ini memungkinkan setiap model dioptimalkan secara independen dan dapat meningkatkan performa, skalabilitas, dan keamanan aplikasi.

Konteks dan masalah

Dalam arsitektur tradisional, model data tunggal sering digunakan untuk operasi baca dan tulis. Pendekatan ini mudah dan cocok untuk operasi membuat, membaca, memperbarui, dan menghapus (CRUD) dasar.

Diagram yang menunjukkan arsitektur CRUD tradisional.

Seiring bertambahnya aplikasi, semakin sulit untuk mengoptimalkan operasi baca dan tulis pada satu model data. Operasi baca dan tulis sering kali memiliki persyaratan performa dan penskalakan yang berbeda. Arsitektur CRUD tradisional tidak memperhitungkan asimetri ini, yang dapat mengakibatkan tantangan berikut:

  • Ketidakcocokan Data: Representasi baca dan tulis data sering berbeda. Beberapa bidang yang diperlukan selama pembaruan mungkin tidak perlu selama operasi baca.

  • Kontensi kunci: Operasi paralel pada himpunan data yang sama dapat menyebabkan kontensi kunci.

  • Masalah performa: Pendekatan tradisional dapat memiliki efek negatif pada performa karena beban pada lapisan penyimpanan data dan akses data, dan kompleksitas kueri yang diperlukan untuk mengambil informasi.

  • Tantangan keamanan: Mungkin sulit untuk mengelola keamanan 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.

Solusi

Gunakan pola CQRS untuk memisahkan operasi tulis, atau perintah, dari operasi baca, atau kueri. Perintah memperbarui data. Kueri mengambil data. Pola CQRS berguna dalam skenario yang memerlukan pemisahan yang jelas antara perintah dan bacaan.

  • Memahami perintah. Perintah harus mewakili tugas bisnis tertentu alih-alih pembaruan data tingkat rendah. Misalnya, di aplikasi pemesanan hotel, gunakan perintah "Pesan kamar hotel" alih-alih "Atur ReservationStatus ke Dipesan." Pendekatan ini lebih baik menangkap niat pengguna dan menyelaraskan perintah dengan proses bisnis. Untuk membantu memastikan bahwa perintah berhasil, Anda mungkin perlu menyempurnakan alur interaksi pengguna dan logika sisi server dan mempertimbangkan pemrosesan asinkron.

    Area penyempurnaan Rekomendasi
    Validasi sisi klien Validasi kondisi tertentu sebelum Anda 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, yang meningkatkan pengalaman mereka.
    Logika sisi server Tingkatkan logika bisnis untuk menangani kasus tepi dan kegagalan dengan anggun. Misalnya, untuk mengatasi kondisi balapan seperti beberapa pengguna yang mencoba memesan ruang terakhir yang tersedia, pertimbangkan untuk menambahkan pengguna ke daftar tunggu atau menyarankan alternatif.
    Pemrosesan asinkron Memproses perintah secara asinkron dengan menempatkannya dalam antrian, alih-alih 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 tanggung jawab yang berbeda ini menyederhanakan desain dan implementasi sistem.

Memisahkan model baca dan model tulis

Memisahkan model baca dari model tulis menyederhanakan desain dan implementasi sistem dengan mengatasi masalah khusus untuk penulisan data dan pembacaan data. Pemisahan ini meningkatkan kejelasan, skalabilitas, dan performa tetapi memperkenalkan kompromi. Misalnya, alat perancah seperti kerangka kerja pemetaan hubungan objek (O/RM) tidak dapat secara otomatis menghasilkan kode CQRS dari skema database, sehingga Anda memerlukan logika kustom untuk menjembatani kesenjangan.

Bagian berikut menjelaskan dua pendekatan utama untuk menerapkan pemisahan model baca dan menulis model di CQRS. Setiap pendekatan memiliki manfaat dan tantangan unik, seperti sinkronisasi dan manajemen konsistensi.

Memisahkan model dalam penyimpanan data tunggal

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. Arsitektur CQRS dasar memungkinkan Anda untuk menggambarkan model tulis dari model baca sambil mengandalkan penyimpanan data bersama.

Diagram yang memperlihatkan arsitektur CQRS dasar.

Pendekatan ini meningkatkan kejelasan, performa, dan skalabilitas dengan mendefinisikan model yang berbeda untuk menangani masalah baca dan tulis.

  • Model tulis dirancang untuk menangani perintah yang memperbarui atau mempertahankan data. Ini termasuk validasi dan logika domain, dan membantu memastikan konsistensi data dengan mengoptimalkan integritas transaksi dan proses bisnis.

  • Model baca dirancang untuk melayani permintaan pengambilan data. Ini berfokus pada pembuatan DTO atau proyeksi yang dioptimalkan untuk lapisan presentasi. Ini meningkatkan performa dan responsivitas kueri dengan menghindari logika domain.

Memisahkan model di penyimpanan data yang berbeda

Implementasi CQRS yang lebih canggih menggunakan penyimpanan data yang berbeda untuk model baca dan tulis. Pemisahan penyimpanan data baca dan tulis memungkinkan Anda menskalakan setiap model 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.

Diagram yang memperlihatkan arsitektur CQRS dengan penyimpanan data baca terpisah dan tulis penyimpanan data.

Saat Anda menggunakan penyimpanan data terpisah, Anda harus memastikan bahwa keduanya tetap disinkronkan. Pola umumnya adalah membuat model tulis menerbitkan peristiwa saat memperbarui database, yang digunakan model baca untuk me-refresh datanya. Untuk informasi selengkapnya tentang cara menggunakan peristiwa, lihat Gaya arsitektur berbasis peristiwa. Karena Anda biasanya tidak dapat mendaftarkan broker pesan dan database ke dalam satu transaksi terdistribusi, tantangan dalam konsistensi dapat terjadi ketika Anda memperbarui database dan menerbitkan peristiwa. Untuk informasi selengkapnya, lihat pemrosesan pesan Idempotent.

Penyimpanan data baca dapat menggunakan skema datanya sendiri yang dioptimalkan untuk kueri. Misalnya, sistem dapat menyimpan tampilan materialized view dari data untuk menghindari gabungan kompleks atau pemetaan O/RM. Penyimpanan data 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.

Manfaat CQRS

  • Penskalakan independen. CQRS memungkinkan model baca dan model tulis untuk diskalakan secara independen. Pendekatan ini dapat membantu meminimalkan ketidakcocokan penguncian 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 kekhawatiran. Memisahkan tanggung jawab baca dan tulis menghasilkan model yang lebih bersih dan lebih dapat dipertahankan. Sisi tulis biasanya menangani logika bisnis yang kompleks. Sisi baca dapat tetap sederhana dan berfokus pada efisiensi penelusuran.

  • Kueri yang lebih sederhana. Saat Anda menyimpan tampilan materialisasi dalam database baca, aplikasi dapat menghindari gabungan kompleks saat kueri.

Masalah dan pertimbangan

Pertimbangkan poin-poin berikut saat Anda memutuskan cara menerapkan pola ini:

  • Peningkatan kompleksitas. Konsep inti CQRS sangat mudah, tetapi dapat memperkenalkan kompleksitas yang signifikan ke dalam desain aplikasi, khususnya jika dikombinasikan dengan pola Sumber Peristiwa.

  • Tantangan olahpesan. Olahpesan bukanlah persyaratan untuk CQRS, tetapi Anda sering menggunakannya untuk memproses perintah dan menerbitkan peristiwa pembaruan. Ketika fitur pesan disertakan, sistem harus memperhitungkan potensi masalah seperti kegagalan pesan, duplikat, dan percobaan ulang. Untuk informasi selengkapnya tentang strategi untuk menangani perintah yang memiliki berbagai prioritas, lihat Antrean prioritas.

  • Konsistensi akhir. Ketika database baca dan database tulis dipisahkan, data baca mungkin tidak segera menampilkan perubahan terbaru. Penundaan ini menghasilkan data kedaluarsa. Memastikan bahwa 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.

Kapan menggunakan pola ini

Gunakan pola ini ketika:

  • Anda bekerja di lingkungan kolaboratif. Di lingkungan di mana 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 konflik apa pun yang terjadi dalam logika perintah.

  • Anda memiliki antarmuka pengguna berbasis tugas. Aplikasi yang memandu pengguna melalui proses kompleks sebagai serangkaian langkah atau dengan model domain kompleks mendapat manfaat dari CQRS.

    • Model penulisan memiliki tumpukan lengkap pemrosesan perintah dengan logika bisnis, validasi input, dan validasi bisnis. Model tulis mungkin memperlakukan sekumpulan objek terkait sebagai satu unit untuk perubahan data, yang dikenal sebagai agregat dalam terminologi desain berbasis domain. Model tulis mungkin juga membantu memastikan bahwa objek ini selalu dalam keadaan konsisten.

    • Model pembacaan tidak memiliki logika bisnis atau mekanisme validasi. Ini mengembalikan DTO untuk digunakan dalam model tampilan. Model baca akhirnya konsisten dengan model tulis.

  • Anda perlu penyetelan performa. Sistem di mana performa baca data harus disempurnakan secara terpisah dari performa penulisan data mendapat manfaat dari CQRS. Pola ini sangat bermanfaat ketika jumlah bacaan lebih besar dari jumlah penulisan. Model pembacaan diskalakan secara horizontal untuk menangani volume kueri besar. Model penulisan berjalan pada lebih sedikit instance untuk meminimalkan konflik penggabungan dan mempertahankan konsistensi.

  • Anda memiliki pemisahan masalah pengembangan. CQRS memungkinkan tim untuk bekerja secara independen. Satu tim mengimplementasikan logika bisnis yang kompleks dalam model tulis, dan tim lain mengembangkan model baca dan komponen antarmuka pengguna.

  • Anda memiliki 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.

  • Anda memerlukan integrasi sistem: Sistem yang terintegrasi dengan subsistem lain, terutama sistem yang menggunakan pola Sumber Peristiwa, tetap tersedia meskipun subsistem untuk sementara gagal. CQRS mengisolasi kegagalan, yang mencegah satu komponen memengaruhi seluruh sistem.

Pola ini mungkin tidak cocok ketika:

  • Domain atau aturan bisnisnya sederhana.

  • Antarmuka pengguna gaya CRUD sederhana dan operasi akses data sudah cukup.

Desain beban kerja

Evaluasi cara menggunakan pola CQRS dalam desain beban kerja untuk mengatasi tujuan dan prinsip yang tercakup dalam pilar Azure Well-Architected Framework. Tabel berikut memberikan panduan tentang bagaimana pola ini mendukung tujuan pilar Efisiensi Performa.

Pilar Bagaimana pola ini mendukung tujuan pilar
Efisiensi Performa membantu beban kerja Anda memenuhi tuntutan secara efisien melalui pengoptimalan dalam penskalaan, data, dan kode. Pemisahan operasi baca dan operasi 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

Pertimbangkan pertukaran terhadap tujuan pilar lain yang mungkin diperkenalkan oleh pola ini.

Menggabungkan pola Pelacakan Peristiwa dan CQRS

Beberapa implementasi CQRS menggabungkan pola Sumber Peristiwa. Pola ini 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 pengaturan ini:

  • Penyimpanan peristiwa adalah model penulisan dan satu sumber kebenaran.

  • Model pembacaan menghasilkan tampilan konkret dari peristiwa ini, biasanya dalam bentuk yang sangat denormalisasi. Tampilan ini mengoptimalkan pengambilan data dengan menyusun struktur yang sesuai dengan kebutuhan kueri dan tampilan.

Manfaat menggabungkan pola Event Sourcing dan CQRS

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 data 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. Pada dasarnya, tampilan materialisasi berfungsi sebagai cache baca-saja tahan lama yang dioptimalkan untuk kueri yang cepat dan efisien.

Pertimbangan tentang cara menggabungkan pola Sumber Peristiwa dan CQRS

Sebelum Anda menggabungkan pola CQRS dengan pola Sumber Peristiwa , evaluasi pertimbangan berikut:

  • Konsistensi akhir: Karena penyimpanan data tulis dan baca terpisah, pembaruan untuk penyimpanan data baca mungkin tertinggal dari pembuatan peristiwa. Penundaan ini menghasilkan konsistensi akhirnya.

  • Peningkatan kompleksitas: Menggabungkan pola CQRS dengan pola 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, pola Sumber Peristiwa 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. Kompleksitas meningkat ketika perhitungan melibatkan analisis atau penjumlahan nilai selama jangka waktu yang lama karena semua peristiwa terkait harus diperiksa. Terapkan rekam jepret data secara berkala. Misalnya, simpan status entitas saat ini atau rekam jepret berkala dari total agregat, yang merupakan berapa kali tindakan tertentu terjadi. Rekam jepret mengurangi kebutuhan untuk memproses riwayat peristiwa lengkap berulang kali, yang meningkatkan performa.

Contoh

Kode berikut menunjukkan ekstrak dari contoh implementasi CQRS yang menggunakan definisi yang berbeda untuk model baca dan model tulis. Antarmuka model tidak menentukan fitur penyimpanan data yang mendasar, dan dapat berevolusi dan disempurnakan secara independen karena antarmuka ini terpisah.

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 melakukan ini dengan menggunakan perintah yang RateProduct 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 ProductsCommandHandler kelas untuk menangani perintah yang dikirim aplikasi. Klien biasanya mengirim perintah ke domain melalui sistem pengiriman pesan 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)
  {
    ...
  }
}

Langkah berikutnya

Informasi berikut mungkin relevan ketika Anda menerapkan pola ini:

  • Panduan pemartisian data menjelaskan praktik terbaik tentang cara membagi data menjadi partisi yang dapat Anda kelola dan akses secara terpisah untuk meningkatkan skalabilitas, mengurangi ketidakcocokan, dan mengoptimalkan performa.
  • Polanya Event Sourcing. Pola ini menjelaskan cara menyederhanakan tugas di domain yang kompleks dan 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. Pola ini menciptakan tampilan yang dimaterialisasi, yang dikenal sebagai tampilan yang dimaterialisasi, untuk kueri yang efisien dan ekstraksi data dari satu atau lebih penyimpanan data. Model baca dari penerapan CQRS dapat berisi tampilan material dari data model tulis, atau model baca dapat digunakan untuk menghasilkan tampilan material.