Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Command Query Responsibility Segregation (CQRS) is een ontwerppatroon waarmee lees- en schrijfbewerkingen voor een gegevensarchief worden gescheiden in afzonderlijke gegevensmodellen. Met deze benadering kan elk model onafhankelijk worden geoptimaliseerd en kunnen de prestaties, schaalbaarheid en beveiliging van een toepassing worden verbeterd.
Context en probleem
In een traditionele architectuur wordt één gegevensmodel vaak gebruikt voor zowel lees- als schrijfbewerkingen. Deze benadering is eenvoudig en is geschikt voor eenvoudige CRUD-bewerkingen (Create, Read, Update en Delete).
Naarmate toepassingen groeien, kan het steeds moeilijker worden om lees- en schrijfbewerkingen op één gegevensmodel te optimaliseren. Lees- en schrijfbewerkingen hebben vaak verschillende prestatie- en schaalvereisten. Bij een traditionele CRUD-architectuur wordt geen rekening gehouden met deze asymmetrie, wat kan leiden tot de volgende uitdagingen:
gegevens komen niet overeen: De lees- en schrijfweergaven van gegevens verschillen vaak. Sommige velden die tijdens updates vereist zijn, zijn mogelijk niet nodig tijdens leesbewerkingen.
Vergrendelingsconflicten: Parallelle bewerkingen op dezelfde gegevensset kunnen leiden tot vergrendelingsconflicten.
Prestatieproblemen: De traditionele benadering kan een negatief effect hebben op de prestaties vanwege de belasting van het gegevensarchief en de laag voor gegevenstoegang, en de complexiteit van query's die nodig zijn om informatie op te halen.
Beveiligingsuitdagingen: Het kan lastig zijn om beveiliging te beheren wanneer entiteiten onderhevig zijn aan lees- en schrijfbewerkingen. Deze overlapping kan gegevens in onbedoelde contexten beschikbaar maken.
Het combineren van deze verantwoordelijkheden kan leiden tot een te ingewikkeld model.
Oplossing
Gebruik het CQRS-patroon om schrijfbewerkingen of opdrachten te scheiden van leesbewerkingen of query's. Opdrachten werken gegevens bij. Query's halen gegevens op. Het CQRS-patroon is handig in scenario's waarvoor een duidelijke scheiding tussen opdrachten en leesbewerkingen is vereist.
Opdrachten begrijpen. Opdrachten moeten specifieke zakelijke taken vertegenwoordigen in plaats van gegevensupdates op laag niveau. Gebruik bijvoorbeeld in een hotelreserverings-app de opdracht 'Hotelkamer boeken' in plaats van 'Set ReservationStatus to Reserved'. Met deze aanpak wordt de intentie van de gebruiker beter vastgelegd en worden opdrachten afgestemd op bedrijfsprocessen. Om ervoor te zorgen dat opdrachten succesvol zijn, moet u mogelijk de gebruikersinteractiestroom en logica aan de serverzijde verfijnen en asynchrone verwerking overwegen.
Verfijningsgebied Aanbeveling Validatie aan clientzijde Valideer specifieke voorwaarden voordat u de opdracht verzendt om duidelijke fouten te voorkomen. Als er bijvoorbeeld geen ruimten beschikbaar zijn, schakelt u de knop Boek uit en geeft u een duidelijk, gebruiksvriendelijk bericht op in de gebruikersinterface waarin wordt uitgelegd waarom boeken niet mogelijk is. Deze installatie vermindert onnodige serveraanvragen en biedt onmiddellijke feedback aan gebruikers, waardoor hun ervaring wordt verbeterd. Logica aan serverzijde Verbeter de bedrijfslogica om edge-aanvragen en fouten probleemloos af te handelen. Als u bijvoorbeeld racevoorwaarden wilt aanpakken, zoals meerdere gebruikers die de laatste beschikbare ruimte willen boeken, kunt u overwegen om gebruikers toe te voegen aan een wachtlijst of alternatieven voor te stellen. Asynchrone verwerking Procesopdrachten asynchroon door ze in een wachtrij te plaatsen in plaats van ze synchroon te verwerken. Begrijpen van vragen. Query's veranderen nooit gegevens. In plaats daarvan retourneren ze DTU's (Data Transfer Objects) die de vereiste gegevens in een handige indeling presenteren, zonder domeinlogica. Deze afzonderlijke scheiding van verantwoordelijkheden vereenvoudigt het ontwerp en de implementatie van het systeem.
Afzonderlijke leesmodellen en schrijfmodellen
Het scheiden van het leesmodel van het schrijfmodel vereenvoudigt het ontwerpen en implementeren van het systeem door specifieke problemen op te lossen voor schrijfbewerkingen en leesbewerkingen van gegevens. Deze scheiding verbetert de duidelijkheid, schaalbaarheid en prestaties, maar introduceert compromissen. Scaffolding tools zoals O/RM-frameworks (object-relational mapping) kunnen bijvoorbeeld niet automatisch CQRS-code genereren op basis van een databaseschema, dus hebt u aangepaste logica nodig om het verschil te overbruggen.
In de volgende secties worden twee primaire benaderingen beschreven voor het implementeren van leesmodel- en schrijfmodelscheiding in CQRS. Elke benadering heeft unieke voordelen en uitdagingen, zoals synchronisatie- en consistentiebeheer.
Afzonderlijke modellen in één gegevensarchief
Deze benadering vertegenwoordigt het fundamentele niveau van CQRS, waarbij zowel de lees- als schrijfmodellen één onderliggende database delen, maar afzonderlijke logica voor hun bewerkingen behouden. Met een eenvoudige CQRS-architectuur kunt u het schrijfmodel uit het leesmodel afbakenen terwijl u afhankelijk bent van een gedeeld gegevensarchief.
Deze aanpak verbetert de duidelijkheid, prestaties en schaalbaarheid door afzonderlijke modellen te definiëren voor het afhandelen van lees- en schrijfproblemen.
Een schrijfmodel is ontworpen voor het verwerken van opdrachten die gegevens bijwerken of behouden. Het omvat validatie- en domeinlogica en helpt bij het garanderen van gegevensconsistentie door te optimaliseren voor transactionele integriteit en bedrijfsprocessen.
Een leesmodel is ontworpen om query's te leveren voor het ophalen van gegevens. Het richt zich op het genereren van DTU's of projecties die zijn geoptimaliseerd voor de presentatielaag. Het verbetert de prestaties en reactiesnelheid van query's door domeinlogica te vermijden.
Afzonderlijke modellen in verschillende gegevensarchieven
Een geavanceerdere CQRS-implementatie maakt gebruik van afzonderlijke gegevensarchieven voor de lees- en schrijfmodellen. Met scheiding van de lees- en schrijfgegevensarchieven kunt u elk model schalen zodat het overeenkomt met de belasting. Hiermee kunt u ook een andere opslagtechnologie gebruiken voor elk gegevensarchief. U kunt een documentdatabase gebruiken voor het leesgegevensarchief en een relationele database voor het schrijfgegevensarchief.
Wanneer u afzonderlijke gegevensarchieven gebruikt, moet u ervoor zorgen dat beide gesynchroniseerd blijven. Een veelvoorkomend patroon is dat het schrijfmodel gebeurtenissen publiceert wanneer de database wordt bijgewerkt, die door het leesmodel wordt gebruikt om de gegevens te vernieuwen. Zie gebeurtenisgestuurde architectuurstijl voor meer informatie over het gebruik van gebeurtenissen. Omdat u meestal geen berichtenbrokers en -databases in één gedistribueerde transactie kunt insluiten, kunnen er problemen met consistentie optreden wanneer u de database en publicatiegebeurtenissen bijwerkt. Zie Idempotente berichtverwerking voor meer informatie.
Het leesgegevensarchief kan een eigen gegevensschema gebruiken dat is geoptimaliseerd voor query's. Het kan bijvoorbeeld een gerealiseerde weergave opslaan van de gegevens om complexe joins of O/RM-toewijzingen te voorkomen. Het leesgegevensarchief kan een alleen-lezen replica van het schrijfarchief zijn of een andere structuur hebben. Het implementeren van meerdere alleen-lezen replica's kan de prestaties verbeteren door latentie te verminderen en de beschikbaarheid te vergroten, met name in gedistribueerde scenario's.
Voordelen van CQRS
Onafhankelijk schalen. Met CQRS kunnen de leesmodellen en schrijfmodellen onafhankelijk worden geschaald. Deze aanpak kan helpen bij het minimaliseren van vergrendelingsconflicten en het verbeteren van de systeemprestaties onder belasting.
Geoptimaliseerde gegevensschema's. Leesbewerkingen kunnen een schema gebruiken dat is geoptimaliseerd voor query's. Schrijfbewerkingen maken gebruik van een schema dat is geoptimaliseerd voor updates.
Beveiliging. Door lees- en schrijfbewerkingen te scheiden, kunt u ervoor zorgen dat alleen de juiste domeinentiteiten of bewerkingen gemachtigd zijn om schrijfacties uit te voeren op de gegevens.
Scheiding van zorgen. Het scheiden van de verantwoordelijkheden voor lezen en schrijven resulteert in schonere, beter onderhoudbare modellen. De schrijfzijde verwerkt doorgaans complexe bedrijfslogica. De leeszijde kan eenvoudig en gericht blijven op de efficiëntie van query's.
Eenvoudigere query's. Wanneer u een gerealiseerde weergave opslaat in de leesdatabase, kan de toepassing complexe joins voorkomen wanneer deze query's uitvoert.
Problemen en overwegingen
Houd rekening met de volgende punten wanneer u besluit hoe u dit patroon implementeert:
Verhoogde complexiteit. Het kernconcept van CQRS is eenvoudig, maar het kan aanzienlijke complexiteit veroorzaken in het toepassingsontwerp, met name in combinatie met het patroon Event Sourcing.
Berichtuitdagingen. Berichten zijn geen vereiste voor CQRS, maar u gebruikt deze vaak om opdrachten te verwerken en update-gebeurtenissen te publiceren. Wanneer berichten worden opgenomen, moet het systeem rekening houden met mogelijke problemen, zoals berichtfouten, duplicaten en nieuwe pogingen. Zie Prioriteitswachtrijen voor meer informatie over strategieën voor het afhandelen van opdrachten met verschillende prioriteiten.
Uiteindelijke consistentie. Wanneer de leesdatabases en schrijfdatabases zijn gescheiden, worden de meest recente wijzigingen mogelijk niet onmiddellijk weergegeven in de leesgegevens. Deze vertraging resulteert in verouderde gegevens. Het kan lastig zijn om ervoor te zorgen dat het leesmodelarchief up-to-date blijft met wijzigingen in het schrijfmodelarchief. Voor het detecteren en verwerken van scenario's waarbij een gebruiker op verouderde gegevens reageert, moet u zorgvuldig rekening houden.
Wanneer gebruikt u dit patroon?
Gebruik dit patroon wanneer:
U werkt in samenwerkingsomgevingen. In omgevingen waarin meerdere gebruikers dezelfde gegevens tegelijk openen en wijzigen, helpt CQRS bij het verminderen van samenvoegingsconflicten. Opdrachten kunnen voldoende granulariteit bevatten om conflicten te voorkomen en het systeem kan conflicten oplossen die optreden in de opdrachtlogica.
Je hebt gebruikersinterfaces op basis van taken. Toepassingen die gebruikers begeleiden bij complexe processen als een reeks stappen of met complexe domeinmodellen profiteren van CQRS.
Het schrijfmodel heeft een volledige stack voor het verwerken van opdrachten met bedrijfslogica, invoervalidatie en bedrijfsvalidatie. Het schrijfmodel kan een set gekoppelde objecten behandelen als één eenheid voor gegevenswijzigingen. Dit wordt een aggregaties genoemd in domeingestuurde ontwerpterminologie. Het schrijfmodel kan er ook voor zorgen dat deze objecten altijd een consistente status hebben.
Het leesmodel heeft geen bedrijfslogica of validatiestack. Het retourneert een DTO voor gebruik in een weergavemodel. Het leesmodel is uiteindelijk consistent met het schrijfmodel.
U hebt prestatie-optimalisatie nodig. Systemen waarbij de prestaties van gegevensleesbewerkingen afzonderlijk moeten worden afgestemd op de prestaties van gegevensschrijfbewerkingen, profiteren van CQRS. Dit patroon is vooral nuttig wanneer het aantal leesbewerkingen groter is dan het aantal schrijfbewerkingen. Het leesmodel wordt horizontaal geschaald om grote queryvolumes te verwerken. Het schrijfmodel wordt uitgevoerd op minder exemplaren om samenvoegingsconflicten te minimaliseren en consistentie te behouden.
U hebt een scheiding van ontwikkelingsproblemen. Met CQRS kunnen teams onafhankelijk werken. Eén team implementeert de complexe bedrijfslogica in het schrijfmodel en een ander team ontwikkelt het leesmodel en de onderdelen van de gebruikersinterface.
Je hebt veranderende systemen. CQRS ondersteunt systemen die zich in de loop van de tijd ontwikkelen. Het biedt plaats aan nieuwe modelversies, frequente wijzigingen in bedrijfsregels of andere wijzigingen zonder dat dit van invloed is op de bestaande functionaliteit.
U hebt systeemintegratie nodig: Systemen die kunnen worden geïntegreerd met andere subsystemen, met name systemen die gebruikmaken van het patroon Gebeurtenisbronnen, blijven beschikbaar, zelfs als een subsysteem tijdelijk uitvalt. CQRS isoleert fouten, waardoor één onderdeel het hele systeem niet beïnvloedt.
Dit patroon is mogelijk niet geschikt wanneer:
Het domein of de bedrijfsregels zijn eenvoudig.
Een eenvoudige CRUD-gebruikersinterface en gegevenstoegangsbewerkingen zijn voldoende.
Werklastontwerp
Evalueer hoe u het CQRS-patroon gebruikt in het ontwerp van een workload om de doelstellingen en principes te verhelpen die worden behandeld in de pijlers van het Azure Well-Architected Framework. De volgende tabel bevat richtlijnen over hoe dit patroon de doelstellingen van de pijler Prestatie-efficiëntie ondersteunt.
Pilaar | Hoe dit patroon ondersteuning biedt voor pijlerdoelen |
---|---|
Prestatie-efficiëntie helpt uw workload efficiënt te voldoen aan de vereisten door optimalisaties in schalen, gegevens en code. | De scheiding van leesbewerkingen en schrijfbewerkingen in workloads met hoge lees-naar-schrijfbewerkingen maakt gerichte prestaties en schaaloptimalisaties mogelijk voor het specifieke doel van elke bewerking. - PE:05 Schalen en verdelen - PE:08 Gegevensprestaties |
Houd rekening met eventuele afwegingen ten opzichte van de doelstellingen van de andere pijlers die dit patroon kan introduceren.
De patronen gebeurtenisbronnen en CQRS combineren
Sommige implementaties van CQRS bevatten het Event Sourcing-patroon. Dit patroon slaat de status van het systeem op als een chronologische reeks gebeurtenissen. Elke gebeurtenis legt de wijzigingen in de gegevens vast op een bepaald tijdstip. Om de huidige status te bepalen, worden deze gebeurtenissen opnieuw afgespeeld in de volgorde van het systeem. In deze installatie:
Het gebeurtenisarchief is het -schrijfmodel en de enige bron van waarheid.
Het leesmodel genereert gerealiseerde weergaven van deze gebeurtenissen, meestal in een sterk gedenormaliseerde vorm. Deze weergaven optimaliseren het ophalen van gegevens door structuren aan te passen aan query- en weergavevereisten.
Voordelen van het combineren van de Event Sourcing- en CQRS-patronen
Dezelfde gebeurtenissen die het schrijfmodel bijwerken, kunnen fungeren als invoer voor het leesmodel. Het leesmodel kan vervolgens een realtime momentopname van de huidige status maken. Deze momentopnamen optimaliseren query's door efficiënte en vooraf samengestelde weergaven van de gegevens te bieden.
In plaats van de huidige status rechtstreeks op te slaan, gebruikt het systeem een stroom gebeurtenissen als het schrijfarchief. Deze aanpak vermindert updateconflicten op aggregaties en verbetert de prestaties en schaalbaarheid. Het systeem kan deze gebeurtenissen asynchroon verwerken om gerealiseerde weergaven voor het leesgegevensarchief te bouwen of bij te werken.
Omdat het gebeurtenisarchief fungeert als de enige bron van waarheid, kunt u eenvoudig gerealiseerde weergaven opnieuw genereren of zich aanpassen aan wijzigingen in het leesmodel door historische gebeurtenissen opnieuw af te spelen. In principe werken gerealiseerde weergaven als een duurzame, alleen-lezen cache die is geoptimaliseerd voor snelle en efficiënte query's.
Overwegingen voor het combineren van Event Sourcing en CQRS-patronen
Voordat u het CQRS-patroon combineert met het patroon gebeurtenisbronnen, moet u de volgende overwegingen evalueren:
Uiteindelijke consistentie: Omdat de gegevensarchieven voor schrijven en lezen gescheiden zijn, kunnen updates voor het leesgegevensarchief achterblijven bij het genereren van gebeurtenissen. Deze vertraging resulteert in uiteindelijke consistentie.
Verhoogde complexiteit: Voor het combineren van het CQRS-patroon met het patroon Gebeurtenisbronnen is een andere ontwerpbenadering vereist, waardoor een succesvolle implementatie lastiger kan worden. U moet code schrijven om gebeurtenissen te genereren, te verwerken en af te handelen en weergaven samen te stellen of bij te werken voor het leesmodel. Het Event Sourcing-patroon vereenvoudigt echter het modelleren van domeinen en stelt u in staat om eenvoudig nieuwe weergaven te bouwen of te creëren door de historie en bedoeling van alle gegevenswijzigingen te behouden.
Prestaties van het genereren van weergaven: Gerealiseerde weergaven genereren voor het leesmodel kan aanzienlijke tijd en resources verbruiken. Hetzelfde geldt voor het projecteren van gegevens door gebeurtenissen opnieuw af te spelen en te verwerken voor specifieke entiteiten of verzamelingen. Complexiteit neemt toe wanneer berekeningen betrekking hebben op het analyseren of optellen van waarden gedurende lange perioden, omdat alle gerelateerde gebeurtenissen moeten worden onderzocht. Implementeer momentopnamen van de gegevens met regelmatige tussenpozen. Sla bijvoorbeeld de huidige status van een entiteit of periodieke momentopnamen van geaggregeerde totalen op. Dit is het aantal keren dat een specifieke actie plaatsvindt. Momentopnamen verminderen de noodzaak om de volledige gebeurtenisgeschiedenis herhaaldelijk te verwerken, waardoor de prestaties worden verbeterd.
Voorbeeld
De volgende code toont extracten uit een voorbeeld van een CQRS-implementatie die gebruikmaakt van verschillende definities voor de leesmodellen en de schrijfmodellen. De modelinterfaces dicteren geen functies van de onderliggende gegevensarchieven en ze kunnen onafhankelijk van elkaar worden aangepast, omdat deze interfaces gescheiden zijn.
De volgende code toont de definitie van het leesmodel.
// 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; }
}
}
Het systeem biedt gebruikers de mogelijkheid om producten te beoordelen. De toepassingscode doet dit met behulp van de RateProduct
opdracht die wordt weergegeven in de volgende code.
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; }
}
Het systeem gebruikt de klasse voor het ProductsCommandHandler
afhandelen van opdrachten die de toepassing verzendt. Clients versturen meestal opdrachten naar het domein via een berichtensysteem zoals een wachtrij. De opdrachthandler accepteert deze opdrachten en roept vervolgens methoden van de domein-interface aan. De granulariteit van elke opdracht is zo ontworpen dat de kans op conflicterende aanvragen zo veel mogelijk wordt beperkt. De volgende code toont een overzicht van de klasse 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)
{
...
}
}
Volgende stap
De volgende informatie is mogelijk relevant wanneer u dit patroon implementeert:
- Richtlijnen voor gegevenspartitionering beschrijven aanbevolen procedures voor het verdelen van gegevens in partities die u afzonderlijk kunt beheren en openen om de schaalbaarheid te verbeteren, conflicten te verminderen en prestaties te optimaliseren.
Verwante middelen
Event Sourcing-patroon. In dit patroon wordt beschreven hoe u taken in complexe domeinen vereenvoudigt en de prestaties, schaalbaarheid en reactiesnelheid verbetert. Ook wordt uitgelegd hoe u consistentie kunt bieden voor transactionele gegevens, terwijl volledige audittrails en -geschiedenis worden onderhouden waarmee compenserende acties kunnen worden ingeschakeld.
Gerealiseerde weergave-patroon. Dit patroon maakt vooraf ingevulde weergaven, ook wel gerealiseerde weergaven genoemd, voor efficiënte query's en gegevensextractie uit een of meer gegevensarchieven. Het leesmodel van een CQRS-implementatie kan gerealiseerde weergaven van de gegevens in het schrijfmodel bevatten, of het leesmodel kan worden gebruikt voor het genereren van gerealiseerde weergaven.