Aracılığıyla paylaş


CQRS düzeni

Command Query Responsibility Segregation (CQRS), bir veri deposu için okuma ve yazma işlemlerini ayrı veri modellerine ayıran bir tasarım desenidir. Bu yaklaşım, her modelin bağımsız olarak iyileştirilmesini sağlar ve bir uygulamanın performansını, ölçeklenebilirliğini ve güvenliğini artırabilir.

Bağlam ve sorun

Geleneksel mimaride tek bir veri modeli genellikle hem okuma hem de yazma işlemleri için kullanılır. Bu yaklaşım basittir ve temel oluşturma, okuma, güncelleştirme ve silme (CRUD) işlemleri için uygundur.

Geleneksel CRUD mimarisini gösteren diyagram.

Uygulamalar büyüdükçe, tek bir veri modelinde okuma ve yazma işlemlerini iyileştirmek giderek zorlaşabilir. Okuma ve yazma işlemlerinin genellikle farklı performans ve ölçeklendirme gereksinimleri vardır. Geleneksel bir CRUD mimarisi bu asimetriyi dikkate almaz ve bu da aşağıdaki zorluklara neden olabilir:

  • Veri uyuşmazlığı: Verilerin okuma ve yazma gösterimleri genellikle farklılık gösterir. Güncelleştirmeler sırasında gerekli olan bazı alanlar, okuma işlemleri sırasında gereksiz olabilir.

  • Kilit çekişmesi: Aynı veri kümesindeki Paralel işlemler kilit çekişmesi neden olabilir.

  • Performans sorunları: Veri deposu ve veri erişim katmanı üzerindeki yük ve bilgileri almak için gereken sorguların karmaşıklığı nedeniyle geleneksel yaklaşımın performans üzerinde olumsuz bir etkisi olabilir.

  • Güvenlik zorlukları: Varlıklar okuma ve yazma işlemlerine tabi olduğunda güvenliği yönetmek zor olabilir. Bu çakışma, verileri istenmeyen bağlamlarda kullanıma açabilir.

Bu sorumlulukların birleştirilmesi aşırı karmaşık bir modele neden olabilir.

Çözüm

Yazma işlemlerini veya komutlarını okuma işlemlerinden veya sorgulardan ayırmak için CQRS desenini kullanın. Komutlar verileri güncelleştirir. Sorgular verileri alır. CQRS düzeni, komutlar ve okumalar arasında net bir ayrım gerektiren senaryolarda kullanışlıdır.

  • Komutları anlama. Komutlar, alt düzey veri güncelleştirmeleri yerine belirli iş görevlerini temsil etmelidir. Örneğin, bir otel rezervasyon uygulamasında "RezervasyonDurumu'yu Ayrılmış Olarak Ayarla" yerine "Otel odası rezervasyonu" komutunu kullanın. Bu yaklaşım kullanıcının amacını daha iyi yakalar ve komutları iş süreçleriyle hizalar. Komutların başarılı olduğundan emin olmak için kullanıcı etkileşim akışını ve sunucu tarafı mantığını geliştirmeniz ve zaman uyumsuz işlemeyi göz önünde bulundurmanız gerekebilir.

    İyileştirme alanı Tavsiye
    İstemci tarafı doğrulama Belirgin hataları önlemek için komutu göndermeden önce belirli koşulları doğrulayın. Örneğin, hiçbir oda yoksa "Rezervasyon Yap" düğmesini devre dışı bırakın ve kullanıcı arabiriminde rezervasyonun neden mümkün olmadığını açıklayan net ve kullanıcı dostu bir mesaj sağlayın. Bu kurulum gereksiz sunucu isteklerini azaltır ve kullanıcılara anında geri bildirim sağlayarak deneyimlerini geliştirir.
    Sunucu tarafı mantığı Uç olguları ve hataları düzgün bir şekilde işlemek için iş mantığını geliştirin. Örneğin, kullanılabilir son odayı ayırmaya çalışan birden çok kullanıcı gibi yarış koşullarını ele almak için kullanıcıları bekleme listesine eklemeyi veya alternatifler önermeyi göz önünde bulundurun.
    Zaman uyumsuz işleme Komutları zaman uyumlu olarak işlemek yerine bir kuyruğa yerleştirerek zaman uyumsuz olarak işleyin.
  • Sorguları anlama. Sorgular hiçbir zaman verileri değiştirmez. Bunun yerine, gerekli verileri herhangi bir etki alanı mantığı olmadan uygun bir biçimde sunan veri aktarım nesnelerini (DTO' lar) döndürür. Bu ayrı sorumluluk ayrımı, sistemin tasarımını ve uygulamasını basitleştirir.

Okuma modellerini ve yazma modellerini ayırma

Okuma modelini yazma modelinden ayırmak, veri yazma ve veri okuma işlemleriyle ilgili belirli endişeleri gidererek sistem tasarımını ve uygulamasını basitleştirir. Bu ayrım netliği, ölçeklenebilirliği ve performansı artırır, ancak bazı fedakarlıklar gerektirir. Örneğin, nesne-ilişkisel eşleme (O/RM) çerçeveleri gibi yapı iskelesi araçları veritabanı şemasından otomatik olarak CQRS kodu oluşturamaz, bu nedenle boşluğu kapatmak için özel mantığa ihtiyacınız vardır.

Aşağıdaki bölümlerde, CQRS'de okuma modeli ve yazma modeli ayrımı uygulamak için iki birincil yaklaşım açıklanmaktadır. Her yaklaşımın eşitleme ve tutarlılık yönetimi gibi benzersiz avantajları ve zorlukları vardır.

Tek bir veri deposundaki modelleri ayırma

Bu yaklaşım, hem okuma hem de yazma modellerinin tek bir temel veritabanını paylaştığı ancak işlemleri için ayrı mantık koruduğu temel CQRS düzeyini temsil eder. Temel bir CQRS mimarisi, paylaşılan bir veri deposuna dayanarak yazma modelini okuma modelinden ayırmanıza imkan sağlar.

Temel bir CQRS mimarisini gösteren diyagram.

Bu yaklaşım, okuma ve yazma sorunlarını işlemek için ayrı modeller tanımlayarak netliği, performansı ve ölçeklenebilirliği artırır.

  • Yazma modeli , verileri güncelleştiren veya kalıcı hale getiren komutları işlemek için tasarlanmıştır. Doğrulama ve etki alanı mantığı içerir ve işlem bütünlüğünü ve iş süreçlerini iyileştirerek veri tutarlılığını sağlamaya yardımcı olur.

  • Okuma modeli , veri almaya yönelik sorgular sunmak üzere tasarlanmıştır. Sunu katmanı için iyileştirilmiş DTO'lar veya projeksiyonlar oluşturmaya odaklanır. Etki alanı mantığını önleyerek sorgu performansını ve yanıt hızını artırır.

Farklı veri depolarında ayrı modeller

Daha gelişmiş bir CQRS uygulaması, okuma ve yazma modelleri için ayrı veri depoları kullanır. Okuma ve yazma veri depolarının ayrılması, her modeli yüke uyacak şekilde ölçeklendirmenize olanak tanır. Ayrıca her veri deposu için farklı bir depolama teknolojisi kullanmanıza da olanak tanır. Okuma veri deposu için bir belge veritabanı ve yazma veri deposu için ilişkisel veritabanı kullanabilirsiniz.

Ayrı okuma veri depolarına ve yazma veri depolarına sahip bir CQRS mimarisini gösteren diyagram.

Ayrı veri depoları kullandığınızda, her ikisinin de eşitlenmiş durumda kaldığından emin olmanız gerekir. Yaygın bir desen, yazma modelinin veritabanını güncelleştirdiğinde olayları yayımlamasını sağlamak ve okuma modelinin bu olayları kullanarak verilerini yenilemesidir. Olayların nasıl kullanılacağı hakkında daha fazla bilgi için bkz . Olay temelli mimari stili. İleti aracılarını ve veritabanlarını genellikle tek bir dağıtılmış işlemde listeleyemezseniz, veritabanını güncelleştirdiğinizde ve olayları yayımladığınızda tutarlılık sorunları oluşabilir. Daha fazla bilgi için bkz. İdempotent ileti işleme.

Okuma veri deposu, sorgular için iyileştirilmiş kendi veri şemasını kullanabilir. Örneğin, karmaşık birleşimleri veya O/RM eşlemelerini önlemek için verilerin gerçekleştirilmiş görünümünü depolayabilir. Okuma veri deposu, yazma deposunun salt okunur bir çoğaltması olabilir veya farklı bir yapıya sahip olabilir. Birden çok salt okunur çoğaltma dağıtmak, özellikle dağıtılmış senaryolarda gecikme süresini azaltarak ve kullanılabilirliği artırarak performansı artırabilir.

CQRS'nin avantajları

  • Bağımsız ölçeklendirme. CQRS, okuma modellerinin ve yazma modellerinin bağımsız olarak ölçeklendirilmesini sağlar. Bu yaklaşım, kilit çekişmelerini en aza indirmeye ve yük altında sistem performansını iyileştirmeye yardımcı olabilir.

  • İyileştirilmiş veri şemaları. Okuma işlemleri, sorgular için iyileştirilmiş bir şema kullanabilir. Yazma işlemleri, güncelleştirmeler için iyileştirilmiş bir şema kullanır.

  • Güvenlik. Okuma ve yazma işlemlerini ayırarak, yalnızca uygun etki alanı varlıklarının veya işlemlerinin veriler üzerinde yazma eylemleri gerçekleştirme iznine sahip olduğundan emin olabilirsiniz.

  • Endişelerin ayrılması. Okuma ve yazma sorumluluklarını ayırmak daha temiz ve daha sürdürülebilir modellere neden olur. Yazma tarafı genellikle karmaşık iş mantığını işler. Okuma tarafı basit kalabilir ve sorgu verimliliğine odaklanabilir.

  • Daha basit sorgular. Gerçekleştirilmiş bir görünümü okuma veritabanında depoladığınızda, uygulama sorgularken karmaşık birleştirmelerden kaçınabilir.

Sorunlar ve dikkat edilmesi gerekenler

Bu düzenin nasıl uygulaneceğine karar velarken aşağıdaki noktaları göz önünde bulundurun:

  • Artan karmaşıklık. CQRS'nin temel kavramı basittir, ancak özellikle Olay Kaynağını Belirleme düzeniyle birleştirildiğinde uygulama tasarımına önemli bir karmaşıklık katabilir.

  • Mesajlaşma zorlukları. Mesajlaşma, CQRS için bir gereksinim değildir, ancak genellikle komutları işlemek ve güncelleştirme olaylarını yayımlamak için kullanırsınız. Mesajlaşma dahil edildiğinde, sistem ileti hataları, yinelemeler ve yeniden denemeler gibi olası sorunları hesaba katmalıdır. Farklı önceliklere sahip komutları işleme stratejileri hakkında daha fazla bilgi için bkz . Öncelik kuyrukları.

  • Nihai tutarlılık. Okuma veritabanları ve yazma veritabanları ayrıldığında, okuma verileri en son değişiklikleri hemen göstermeyebilir. Bu gecikme, bayat verilere neden olur. Okuma modeli deposunun yazma modeli deposundaki değişikliklerle up-togüncel kalmasını sağlamak zor olabilir. Ayrıca, kullanıcının eski veriler üzerinde hareket ettiği senaryoları algılamak ve işlemek için dikkatli bir değerlendirme gerekir.

Bu desen ne zaman kullanılır?

Bu düzeni aşağıdaki durumlarda kullanın:

  • İşbirliğine dayalı ortamlarda çalışıyorsunuz. Birden çok kullanıcının aynı verilere aynı anda eriştiği ve bunları değiştirdiği ortamlarda, CQRS birleştirme çakışmalarını azaltmaya yardımcı olur. Komutlar çakışmaları önlemek için yeterli ayrıntı düzeyi içerebilir ve sistem komut mantığı içinde oluşan çakışmaları çözebilir.

  • Görev tabanlı kullanıcı arabirimleriniz var. Kullanıcılara bir dizi adım olarak veya karmaşık etki alanı modelleriyle karmaşık işlemler boyunca yol gösteren uygulamalar CQRS'den yararlanıyor.

    • Yazma modeli, iş mantığı, giriş doğrulaması ve iş doğrulama ile tam bir komut işleme yığınına sahiptir. Yazma modeli, etki alanı temelli tasarım terminolojisinde toplama olarak bilinen veri değişiklikleri için ilişkili nesneler kümesini tek bir birim olarak değerlendirebilir. Yazma modeli, bu nesnelerin her zaman tutarlı bir durumda olmasını sağlamaya da yardımcı olabilir.

    • Okuma modelinin iş mantığı veya doğrulama yığını yoktur. Görünüm modelinde kullanılmak üzere bir DTO döndürür. Okuma modeli, nihai olarak yazma modeliyle tutarlıdır.

  • Performans optimizasyonuna ihtiyacınız var. Veri okuma performansının veri yazma performansından ayrı olarak ince ayar yapılması gereken sistemler, CQRS'den faydalanır. Bu düzen özellikle okuma sayısı yazma sayısından fazla olduğunda faydalıdır. Okuma modeli, büyük sorgu birimlerini işlemek için yatay olarak ölçeklendirilir. Yazma modeli, birleştirme çakışmalarını en aza indirmek ve tutarlılığı korumak için daha az örnek üzerinde çalışır.

  • Gelişim endişelerini birbirinden ayırmışsınız. CQRS, ekiplerin bağımsız olarak çalışmasına olanak tanır. Bir ekip yazma modelinde karmaşık iş mantığını uygularken, başka bir ekip okuma modeli ve kullanıcı arabirimi bileşenlerini geliştirir.

  • Gelişen sistemleriniz var. CQRS, zaman içinde gelişen sistemleri destekler. Yeni model sürümlerine, iş kurallarında sık yapılan değişikliklere veya mevcut işlevleri etkilemeden diğer değişikliklere uyum sağlar.

  • Sistem tümleştirmesi gerekir: Diğer alt sistemlerle tümleşen sistemler, özellikle de Olay Kaynağını Belirleme düzenini kullanan sistemler, bir alt sistem geçici olarak başarısız olsa bile kullanılabilir durumda kalır. CQRS hataları yalıtarak tek bir bileşenin sistemin tamamını etkilemesini önler.

Bu düzen aşağıdaki durumlarda uygun olmayabilir:

  • Etki alanı veya iş kuralları basittir.

  • Basit bir CRUD stili kullanıcı arabirimi ve veri erişim işlemleri yeterlidir.

İş yükü tasarımı

Azure Well-Architected Framework yapılarında ele alınan hedefleri ve ilkeleri ele almak için bir iş yükünün tasarımında CQRS deseninin nasıl kullanılacağını değerlendirin. Aşağıdaki tabloda, bu desenin Performans Verimliliği sütununun hedeflerini nasıl desteklediği hakkında rehberlik sağlanmaktadır.

Sütun Bu desen sütun hedeflerini nasıl destekler?
Performans Verimliliği , ölçeklendirme, veri ve kod iyileştirmeleri aracılığıyla iş yükünüzün talepleri verimli bir şekilde karşılamasını sağlar. Okuma işlemlerinin ve yazma işlemlerinin yüksek okuma-yazma iş yüklerinde ayrılması, her işlemin belirli bir amacı için hedeflenen performans ve ölçeklendirme iyileştirmelerine olanak tanır.

- PE:05 Ölçeklendirme ve bölümleme
- PE:08 Veri performansı

Bu düzenin ortaya çıkarabileceği diğer sütunların hedeflerine karşı ödünleri göz önünde bulundurun.

Olay Kaynaklama ve CQRS desenlerini birleştirin

CQRS'nin bazı uygulamaları Olay Kaynağını Belirleme desenini içerir. Bu düzen, sistemin durumunu kronolojik bir dizi olay olarak depolar. Her olay, verilerde yapılan değişiklikleri belirli bir zamanda yakalar. Sistem, geçerli durumu belirlemek için bu olayları sırayla yeniden yürütmektedir. Bu kurulumda:

  • Olay deposu, yazma modeli ve tek gerçeklik kaynağıdır.

  • okuma modeli bu olaylardan genellikle yüksek oranda normal olmayan bir biçimde gerçekleştirilmiş görünümler oluşturur. Bu görünümler, yapıları sorgulama ve görüntüleme gereksinimlerini karşılayacak şekilde uyarlayarak veri alımını en iyi duruma getirmektedir.

Olay Kaynağını Belirleme ve CQRS desenlerini birleştirmenin avantajları

Yazma modelini güncelleştiren olaylar, okuma modeline giriş görevi görebilir. Okuma modeli daha sonra geçerli durumun gerçek zamanlı anlık görüntüsünü oluşturabilir. Bu anlık görüntüler, verilerin verimli ve önceden derlenmiş görünümlerini sağlayarak sorguları iyileştirir.

Sistem, geçerli durumu doğrudan depolamak yerine yazma deposu olarak bir olay akışı kullanır. Bu yaklaşım, toplamalardaki güncelleştirme çakışmalarını azaltır ve performans ile ölçeklenebilirliği artırır. Sistem, veri okuma deposu için gerçekleştirilmiş görünümler oluşturmak veya güncelleştirmek amacıyla bu olayları eşzamanlı olmayan bir şekilde işleyebilir.

Olay deposu tek bir gerçeklik kaynağı işlevi görerek gerçekleştirilmiş görünümleri kolayca yeniden oluşturabilir veya geçmiş olayları yeniden yürüterek okuma modelindeki değişikliklere uyarlayabilirsiniz. Temelde gerçekleştirilmiş görünümler, hızlı ve verimli sorgular için optimize edilmiş, dayanıklı ve salt okunur bir önbellek işlevi görür.

Olay Kaynağını Belirleme ve CQRS desenlerini birleştirme konusunda dikkat edilmesi gerekenler

CQRS desenini Olay Kaynağını Belirleme desenibirleştirmeden önce aşağıdaki noktaları değerlendirin:

  • Nihai tutarlılık: Yazma ve okuma veri depoları ayrı olduğundan, okuma veri deposu güncelleştirmeleri olay oluşturmanın gerisinde kalabilir. Bu gecikme, nihai tutarlılığa neden olur.

  • Artan karmaşıklık: CQRS desenini Olay Kaynağını Belirleme düzeniyle birleştirmek farklı bir tasarım yaklaşımı gerektirir ve bu da başarılı bir uygulamayı daha zor hale getirir. Olayları oluşturmak, işlemek ve yönetmek, ve okuma modeli için görünümleri derlemek veya güncelleştirmek için kod yazmanız gerekir. Ancak Olay Kaynağını Belirleme düzeni etki alanı modellemesini basitleştirir ve tüm veri değişikliklerinin geçmişini ve amacını koruyarak kolayca yeniden oluşturmanızı veya yeni görünümler oluşturmanızı sağlar.

  • Görünüm oluşturma performansı: Okuma modeli için gerçekleştirilmiş görünümler oluşturmak önemli ölçüde zaman ve kaynak tüketebilir. Aynı durum, belirli varlıklar veya koleksiyonlar için olayları yeniden yürüterek ve işleyerek verileri yansıtmak için de geçerlidir. Hesaplamalar, tüm ilgili olayların incelenmesi gerektiğinden, uzun dönemlerdeki değerleri analiz etmeyi veya toplamayı içerdiğinde karmaşıklık artar. Düzenli aralıklarla verilerin anlık görüntülerini uygulayın. Örneğin, bir varlığın geçerli durumunu veya belirli bir eylemin gerçekleşme sayısı olan toplu toplamların düzenli anlık görüntülerini depolayın. Anlık görüntüler, tüm olay geçmişini tekrar tekrar işleme gereksinimini azaltarak performansı artırır.

Örnek

Aşağıdaki kod, okuma modelleri ve yazma modelleri için farklı tanımlar kullanan bir CQRS uygulaması örneğinden ayıklananları gösterir. Model arabirimleri, temel alınan veri depolarının özelliklerini dikte etmediğinden, bu arabirimler ayrı olduğundan gelişebilir ve bağımsız olarak ince ayar yapılabilir.

Aşağıdaki kodda okuma modeli tanımı gösterilmiştir.

// 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, kullanıcıların ürünleri derecelendirmesine izin verir. Uygulama kodu bunu aşağıdaki kodda gösterilen komutu kullanarak RateProduct yapar.

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, uygulamanın gönderdiği komutları işlemek için sınıfını kullanır ProductsCommandHandler . İstemciler komutları genellikle kuyruk gibi bir mesajlaşma sistemi üzerinden etki alanına gönderir. Komut işleyici bu komutları kabul eder ve etki alanı arabiriminin metotlarını çağırır. Her komutun ayrıntı düzeyi, isteklerin çakışma olasılığını azaltmaya yönelik olarak tasarlanır. Aşağıdaki kodda bir ProductsCommandHandler sınıfının ana hattı gösterilmiştir.

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)
  {
    ...
  }
}

Sonraki adım

Bu düzeni uyguladığınızda aşağıdaki bilgiler ilgili olabilir:

  • Veri bölümleme kılavuzu , ölçeklenebilirliği geliştirmek, çekişmeyi azaltmak ve performansı iyileştirmek için verileri yönetebileceğiniz ve erişebileceğiniz bölümlere bölmeye yönelik en iyi yöntemleri açıklar.
  • Olay Kaynağını Belirleme düzeni. Bu desen karmaşık etki alanlarındaki görevleri basitleştirmeyi ve performansı, ölçeklenebilirliği ve yanıt hızını artırmayı açıklar. Ayrıca, telafi eylemlerini etkinleştirebilecek tam denetim kayıtlarını ve geçmişini korurken işlem verileri için tutarlılık sağlamayı da açıklar.

  • Gerçekleştirilmiş Görünüm düzeni. Bu desen, bir veya daha fazla veri deposundan verimli sorgulama ve veri ayıklama için maddileştirilmiş görünümler olarak bilinen önceden doldurulmuş görünümler oluşturur. Bir CQRS uygulamasının okuma modeli, yazma modeli verilerinin gerçekleştirilmiş görünümlerini içerebilir ya da gerçekleştirilmiş görünümlerin oluşturulması için okuma modeli kullanılabilir.