Sdílet prostřednictvím


Vzorec CQRS

Oddělení odpovědnosti příkazového dotazu (CQRS) je vzor návrhu, který odděluje operace čtení a zápisu pro úložiště dat do samostatných datových modelů. Tento přístup umožňuje nezávisle optimalizovat každý model a může zlepšit výkon, škálovatelnost a zabezpečení aplikace.

Kontext a problém

V tradiční architektuře se jeden datový model často používá pro operace čtení i zápisu. Tento přístup je jednoduchý a je vhodný pro základní operace vytvoření, čtení, aktualizace a odstranění (CRUD).

Diagram znázorňující tradiční architekturu CRUD

S růstem aplikací může být stále obtížnější optimalizovat operace čtení a zápisu v jednom datovém modelu. Operace čtení a zápisu mají často různé požadavky na výkon a škálování. Tradiční architektura CRUD tuto asymetrii nebere v úvahu, což může vést k následujícím výzvám:

  • neshoda dat : Reprezentace dat pro čtení a zápis se často liší. Některá pole, která jsou vyžadována během aktualizací, můžou být při operacích čtení nepotřebná.

  • Kolize zámků: Paralelní operace ve stejné sadě dat můžou způsobit kolize zámků.

  • Problémy s výkonem: Tradiční přístup může mít negativní vliv na výkon kvůli zatížení vrstvy úložiště dat a přístupu k datům a složitosti dotazů potřebných k načtení informací.

  • Výzvy zabezpečení: Zabezpečení může být obtížné spravovat, když entity podléhají operacím čtení a zápisu. Toto překrytí může zpřístupnit data v nezamýšlených kontextech.

Kombinace těchto zodpovědností může vést k příliš složitému modelu.

Řešení

Model CQRS slouží k oddělení operací zápisu nebo příkazů od operací čtení nebo dotazů. Příkazy aktualizují data. Dotazy získávají data. Model CQRS je užitečný ve scénářích, které vyžadují jasné oddělení mezi příkazy a čtením.

  • Seznamte se s příkazy. Příkazy by měly místo aktualizací dat nízké úrovně představovat konkrétní obchodní úlohy. Například v aplikaci pro rezervaci hotelu použijte příkaz "Rezervovat hotelový pokoj" místo "Nastavit reservationStatus na rezervovaný". Tento přístup lépe zachycuje záměr uživatele a zarovnává příkazy s obchodními procesy. Abyste zajistili, že příkazy budou úspěšné, budete možná muset upřesnit tok interakce uživatele a logiku na straně serveru a zvážit asynchronní zpracování.

    Oblast zdokonalování Doporučení
    Ověřování na straně klienta Před odesláním příkazu ověřte konkrétní podmínky, abyste zabránili zjevnému selhání. Pokud například nejsou k dispozici žádné místnosti, zneaktivněte tlačítko "Rezervovat" a v uživatelském rozhraní zobrazte srozumitelnou a přívětivou zprávu, která vysvětluje, proč rezervace není možná. Toto nastavení snižuje nepotřebné požadavky na server a poskytuje uživatelům okamžitou zpětnou vazbu, což zlepšuje jejich prostředí.
    Logika na straně serveru Vylepšete obchodní logiku, abyste mohli řádně zpracovávat hraniční případy a selhání. Pokud například chcete řešit závodní podmínky, jako je situace, kdy se více uživatelů pokouší rezervovat poslední dostupnou místnost, zvažte přidání uživatelů do seznamu čekatelů nebo navržení alternativ.
    Asynchronní zpracování Zpracování příkazů asynchronně jejich umístěním do fronty místo synchronního zpracování příkazů.
  • Porozumět dotazům. Dotazy nikdy nemění data. Místo toho vrátí objekty pro přenos dat (DTO), které představují požadovaná data ve vhodném formátu bez jakékoli logiky domény. Toto odlišné oddělení odpovědností zjednodušuje návrh a implementaci systému.

Samostatné modely čtení a modely zápisu

Oddělení modelu čtení od modelu zápisu zjednodušuje návrh a implementaci systému tím, že řeší konkrétní obavy týkající se zápisů dat a čtení dat. Toto oddělení zlepšuje přehlednost, škálovatelnost a výkon, ale přináší kompromisy. Například nástroje pro generování uživatelského rozhraní, jako jsou architektury objektově-relačního mapování (O/RM), nemůžou automaticky generovat kód CQRS ze schématu databáze, takže k překlenutí mezery potřebujete vlastní logiku.

Následující části popisují dva primární přístupy k implementaci modelu čtení a oddělení modelů zápisu v CQRS. Každý přístup má jedinečné výhody a výzvy, jako je synchronizace a správa konzistence.

Samostatné modely v jednom úložišti dat

Tento přístup představuje základní úroveň CQRS, kde modely čtení i zápisu sdílejí jednu podkladovou databázi, ale udržují pro své operace odlišnou logiku. Základní architektura CQRS umožňuje delineovat model zápisu z modelu čtení při závislosti na sdíleném úložišti dat.

Diagram znázorňující základní architekturu CQRS

Tento přístup zlepšuje přehlednost, výkon a škálovatelnost definováním jedinečných modelů pro zpracování problémů se čtením a zápisem.

  • Model zápisu je navržený tak, aby zpracovával příkazy, které aktualizují nebo uchovávají data. Zahrnuje ověřování a logiku domény a pomáhá zajistit konzistenci dat optimalizací pro transakční integritu a obchodní procesy.

  • Model pro čtení je navržený tak, aby sloužil dotazům pro načítání dat. Zaměřuje se na generování DTO nebo projekce, které jsou optimalizované pro prezentační vrstvu. Zvyšuje výkon dotazů a rychlost odezvy tím, že se vyhne logikě domény.

Samostatné modely v různých úložištích dat

Pokročilejší implementace CQRS používá pro modely čtení a zápisu odlišné úložiště dat. Oddělení úložišť dat pro čtení a zápis umožňuje škálovat každý model tak, aby odpovídal zatížení. Umožňuje také používat pro každé úložiště dat jinou technologii úložiště. Pro úložiště dat pro čtení a relační databázi pro úložiště dat zápisu můžete použít databázi dokumentů.

Diagram znázorňující architekturu CQRS se samostatnými úložišti pro čtení dat a zápis dat.

Pokud používáte samostatná úložiště dat, musíte zajistit, aby obě zůstaly synchronizované. Běžným modelem je publikování událostí modelu zápisu při aktualizaci databáze, kterou model čtení používá k aktualizaci dat. Další informace o použití událostí naleznete ve stylu architektury řízené událostmi. Vzhledem k tomu, že obvykle nemůžete zapsat zprostředkovatele zpráv a databáze do jedné distribuované transakce, může při aktualizaci databáze a publikování událostí dojít k problémům v konzistenci. Další informace naleznete v tématu Idempotentní zpracování zpráv.

Úložiště dat pro čtení může používat vlastní schéma dat optimalizované pro dotazy. Může například uložit materializované zobrazení dat, aby se vyhnulo složitým spojováním nebo mapováním relací objektů. Úložiště dat pro čtení může být replikou úložiště pro zápis, nebo může mít jinou strukturu. Nasazení několika replik jen pro čtení může zlepšit výkon snížením latence a zvýšením dostupnosti, zejména v distribuovaných scénářích.

Výhody CQRS

  • Nezávislé škálování CQRS umožňuje nezávislé škálování modelů čtení a zápisu. Tento přístup může pomoci minimalizovat konflikt zámků a zlepšit výkon systému při zatížení.

  • Optimalizovaná schémata dat. Operace čtení můžou používat schéma optimalizované pro dotazy. Operace zápisu používají schéma optimalizované pro aktualizace.

  • Bezpečnost. Oddělením čtení a zápisů můžete zajistit, že oprávnění k provádění akcí zápisu s daty mají pouze příslušné entity domény nebo operace.

  • Oddělení odpovědností. Oddělení odpovědností za čtení a zápis vede k čistějším a lépe udržovatelným modelům. Na straně zápisu se obvykle zpracovává složitá obchodní logika. Stránka pro čtení může zůstat jednoduchá a zaměřená na efektivitu dotazů.

  • Jednodušší dotazy. Při ukládání materializovaného zobrazení do databáze pro čtení se aplikace při dotazech může vyhnout složitým spojením.

Problémy a důležité informace

Při rozhodování o implementaci tohoto modelu zvažte následující body:

  • Zvýšená složitost. Základní koncept CQRS je jednoduchý, ale může do návrhu aplikace zavádět značné složitosti, konkrétně v kombinaci se vzorem Event Sourcing.

  • Problémy se zasíláním zpráv Zasílání zpráv není požadavkem pro CQRS, ale často ho používáte ke zpracování příkazů a publikování událostí aktualizace. Při zahrnutí zasílání zpráv musí systém počítat s potenciálními problémy, jako jsou selhání zpráv, duplicity a opakování. Další informace o strategiích pro zpracování příkazů, které mají různé priority, najdete v tématu Prioritní fronty.

  • Konečná konzistence Když jsou databáze pro čtení a zápis databáze oddělené, data čtení nemusí okamžitě zobrazovat nejnovější změny. Výsledkem tohoto zpoždění jsou zastaralá data. Zajištění toho, aby úložiště modelu čtení zůstalo up-to-date s změnami v úložišti modelů zápisu může být náročné. Zjišťování a zpracování scénářů, ve kterých uživatel pracuje se zastaralými daty, také vyžaduje pečlivé zvážení.

Kdy použít tento vzor

Tento model použijte v těchto případech:

  • Pracujete v prostředích pro spolupráci. V prostředích, kde více uživatelů přistupuje k datům a současně je upravuje, CQRS pomáhá snižovat konflikty při slučování změn. Příkazy můžou obsahovat dostatečnou členitost, aby se zabránilo konfliktům, a systém dokáže vyřešit případné konflikty, ke kterým dochází v rámci logiky příkazu.

  • Máte uživatelská rozhraní založená na úlohách. Aplikace, které uživatele provedou složitými procesy jako řadu kroků nebo s komplexními doménovými modely, získají výhodu CQRS.

    • Model zápisu má kompletní zásobník pro zpracování příkazů s obchodní logikou, ověřováním vstupu a obchodním ověřením. Model zápisu může považovat sadu přidružených objektů za jedinou jednotku pro změny dat, která se v terminologii návrhu řízené doménou označuje jako agregace . Model zápisu může také pomoct zajistit, aby tyto objekty byly vždy v konzistentním stavu.

    • Model pro čtení nemá žádnou obchodní logiku ani validaci. Vrátí DTO pro použití v zobrazovacím modelu. Model čtení má konečnou konzistenci s modelem zápisu.

  • Potřebujete optimalizaci výkonu. Systémy, kde musí být výkon čtení dat jemně vyladěn odděleně od výkonu zápisů dat, těží z CQRS. Tento model je zvlášť užitečný v případě, že je počet čtení větší než počet zápisů. Model pro čtení se horizontálně škáluje tak, aby zpracovával velké objemy dotazů. Model zápisu běží na méně instancích, aby se minimalizovaly konflikty při slučování a zachovala konzistence.

  • Máte oddělení otázek vývoje. CQRS umožňuje týmům pracovat nezávisle. Jeden tým implementuje komplexní obchodní logiku v modelu zápisu a jiný tým vyvíjí model čtení a komponenty uživatelského rozhraní.

  • Máte vyvíjející se systémy. CQRS podporuje systémy, které se v průběhu času vyvíjejí. Obsahuje nové verze modelu, časté změny obchodních pravidel nebo jiné úpravy, aniž by to mělo vliv na stávající funkce.

  • Potřebujete integraci systému: Systémy, které se integrují s jinými subsystémy, zejména systémy, které používají model Event Sourcing, zůstanou dostupné i v případě, že subsystém dočasně selže. CQRS izoluje selhání, což brání tomu, aby jedna komponenta ovlivnila celý systém.

Tento vzor nemusí být vhodný v těchto případech:

  • Doména nebo obchodní pravidla jsou jednoduchá.

  • Stačí jednoduché uživatelské rozhraní ve stylu CRUD a operace přístupu k datům.

Návrh úloh

Vyhodnoťte, jak používat model CQRS v návrhu úlohy k řešení cílů a principů popsaných v pilířích architektury Azure Well-Architected Framework. Následující tabulka obsahuje pokyny k tomu, jak tento model podporuje cíle pilíře Efektivita výkonu.

Pilíř Jak tento model podporuje cíle pilíře
Efektivita výkonu pomáhá vaší úloze efektivně splňovat požadavky prostřednictvím optimalizací škálování, dat a kódu. Oddělení operací čtení a operací zápisu v úlohách s vysokým počtem čtení a zápisu umožňuje cílené optimalizace výkonu a škálování pro konkrétní účel každé operace.

- PE:05 Škálování a dělení
- Výkon dat PE:08

Zvažte všechny kompromisy proti cílům ostatních pilířů, které by tento model mohl zavést.

Kombinování vzorů Event Sourcing a CQRS

Některé implementace CQRS zahrnují vzor Event Sourcing. Tento model ukládá stav systému jako chronologickou řadu událostí. Každá událost zaznamenává změny provedené v datech v určitém čase. Aby systém určil aktuální stav, přehraje tyto události v pořadí. V tomto nastavení:

  • Úložiště událostí je zapisovací model a jediný zdroj pravdy.

  • Model čtecí generuje materializovaná zobrazení z těchto událostí, obvykle ve vysoce denormalizované podobě. Tato zobrazení optimalizují načítání dat přizpůsobením struktur pro dotazování a zobrazení požadavků.

Výhody kombinování vzorů Event Sourcing a CQRS

Stejné události, které aktualizují model zápisu, můžou sloužit jako vstupy do modelu čtení. Model pro čtení pak může vytvořit snímek aktuálního stavu v reálném čase. Tyto snímky optimalizují dotazy tím, že poskytují efektivní a předem propočítané zobrazení dat.

Místo přímého ukládání aktuálního stavu používá systém jako úložiště zápisu datový proud událostí. Tento přístup snižuje konflikty aktualizací u agregací a zvyšuje výkon a škálovatelnost. Systém může tyto události zpracovat asynchronně za účelem sestavení nebo aktualizace materializovaných zobrazení pro úložiště dat pro čtení.

Vzhledem k tomu, že úložiště událostí funguje jako jediný zdroj pravdy, můžete snadno znovu vygenerovat materializovaná zobrazení nebo se přizpůsobit změnám v modelu čtení tak, že přehrajete historické události. Materializovaná zobrazení v podstatě fungují jako odolná mezipaměť jen pro čtení, která je optimalizovaná pro rychlé a efektivní dotazy.

Důležité informace o kombinování vzorů Event Sourcing a CQRS

Než zkombinujete model CQRS se vzorem Event Sourcing, vyhodnoťte následující aspekty:

  • Konečná konzistence: Vzhledem k tomu, že úložiště dat pro zápis a čtení jsou oddělená, mohou být aktualizace úložiště dat pro čtení oproti generování událostí opožděné. Výsledkem tohoto zpoždění je nakonec dosažená konzistence.

  • Zvýšená složitost: Kombinace modelu CQRS se vzorem Event Sourcing vyžaduje jiný přístup k návrhu, který může ztížit úspěšnou implementaci. Je nutné napsat kód pro generování, zpracování a zpracování událostí a sestavení nebo aktualizace zobrazení pro model čtení. Model Event Sourcing však zjednodušuje modelování domény a umožňuje snadné opětovné sestavení nebo vytvoření nových zobrazení zachováním historie a záměru všech změn dat.

  • Výkon generování zobrazení: Generování materializovaných zobrazení pro model čtení může spotřebovávat významný čas a prostředky. Totéž platí pro projektování dat přehráním a zpracováním událostí pro konkrétní entity nebo kolekce. Složitost se zvyšuje, když výpočty zahrnují analýzu nebo součet hodnot za dlouhá období, protože je potřeba prozkoumat všechny související události. Implementujte snímky dat v pravidelných intervalech. Například uložte aktuální stav entity nebo pravidelné snímky agregovaných souhrnů, což značí počet vykonání specifické akce. Snímky snižují potřebu opakovaného zpracování celé historie událostí, což zlepšuje výkon.

Příklad

Následující kód ukazuje extrakci z příkladu implementace CQRS, která používá různé definice pro modely čtení a modely zápisu. Rozhraní modelu nediktují funkce podkladových úložišť dat a můžou se vyvíjet a být jemně vyladěné nezávisle, protože tato rozhraní jsou oddělená.

Následující kód obsahuje definici modelu čtení.

// 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; }
  }
}

Systém umožňuje uživatelům hodnocení produktů. Kód aplikace to provede pomocí RateProduct příkazu uvedeného v následujícím kódu.

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; }
}

Systém používá ProductsCommandHandler třídu ke zpracování příkazů, které aplikace odesílá. Klienti odesílají příkazy do domény obvykle prostřednictvím systému zasílání zpráv, jako je třeba fronta. Obslužná rutina příkazu přijímá tyto příkazy a volá metody pro rozhraní domény. Úroveň podrobností každého příkazu je navržená tak, aby se omezil výskyt konfliktních požadavků. Následující kód ukazuje osnovu třídy 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)
  {
    ...
  }
}

Další krok

Při implementaci tohoto modelu můžou být relevantní následující informace:

  • Pokyny k dělení dat popisují osvědčené postupy pro rozdělení dat do oddílů, ke kterým můžete spravovat a přistupovat samostatně, abyste zlepšili škálovatelnost, snížili kolize a optimalizovali výkon.
  • Vzorec Event Sourcing. Tento model popisuje, jak zjednodušit úlohy ve složitých doménách a zlepšit výkon, škálovatelnost a rychlost odezvy. Vysvětluje také, jak zajistit konzistenci transakčních dat při zachování úplných tras auditu a historie, které umožňují kompenzační akce.

  • Vzorec Materializovaného pohledu. Tento model vytvoří předem vyplněná zobrazení označovaná jako materializovaná zobrazení pro efektivní dotazování a extrakci dat z jednoho nebo více úložišť dat. Model čtení v implementaci modelu CQRS může obsahovat materializovaná zobrazení dat modelu zápisu, případně lze ke generování materializovaných zobrazení daný model čtení použít.