Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
CQRS (Command Query Responsibility Segregation) är ett designmönster som separerar läs- och skrivåtgärder för ett datalager i separata datamodeller. Med den här metoden kan varje modell optimeras oberoende av varandra och kan förbättra prestanda, skalbarhet och säkerhet för ett program.
Kontext och problem
I en traditionell arkitektur används ofta en enda datamodell för både läs- och skrivåtgärder. Den här metoden är enkel och passar för grundläggande crud-åtgärder (create, read, update och delete).
När programmen växer kan det bli allt svårare att optimera läs- och skrivåtgärder på en enda datamodell. Läs- och skrivåtgärder har ofta olika prestanda- och skalningskrav. En traditionell CRUD-arkitektur tar inte hänsyn till den här asymmetrin, vilket kan leda till följande utmaningar:
Datamatchningsfel: Läs- och skrivrepresentationerna av data skiljer sig ofta åt. Vissa fält som krävs under uppdateringar kan vara onödiga under läsåtgärder.
Lås konflikter: Parallella åtgärder på samma datauppsättning kan orsaka låskonflikt.
Prestandaproblem: Den traditionella metoden kan ha en negativ effekt på prestanda på grund av belastningen på datalagret och dataåtkomstskiktet och komplexiteten i frågor som krävs för att hämta information.
Säkerhetsutmaningar: Det kan vara svårt att hantera säkerheten när entiteter omfattas av läs- och skrivåtgärder. Den här överlappningen kan exponera data i oavsiktliga kontexter.
Att kombinera dessa ansvarsområden kan resultera i en alltför komplicerad modell.
Lösning
Använd CQRS-mönstret för att separera skrivåtgärder eller kommandon från läsåtgärder eller frågor. Kommandon uppdaterar data. Sökfrågor hämtar data. CQRS-mönstret är användbart i scenarier som kräver en tydlig uppdelning mellan kommandon och läsningar.
Förstå kommandon. Kommandon bör representera specifika affärsuppgifter i stället för datauppdateringar på låg nivå. I en hotellbokningsapp använder du till exempel kommandot "Boka hotellrum" i stället för "Ange ReservationStatus till Reserverad". Den här metoden fångar bättre upp användarens avsikt och justerar kommandon med affärsprocesser. För att säkerställa att kommandona lyckas kan du behöva förfina användarinteraktionsflödet och logiken på serversidan och överväga asynkron bearbetning.
Förfiningsområde Rekommendation Validering på klientsidan Verifiera specifika villkor innan du skickar kommandot för att förhindra uppenbara fel. Om det till exempel inte finns några tillgängliga rum inaktiverar du knappen "Boka" och ger ett tydligt, användarvänligt meddelande i användargränssnittet som förklarar varför det inte går att boka. Den här konfigurationen minskar onödiga serverbegäranden och ger omedelbar feedback till användarna, vilket förbättrar deras upplevelse. Logik på serversidan Förbättra affärslogik för att hantera gränsfall och fel på ett korrekt sätt. Om du till exempel vill ta itu med konkurrensförhållanden som flera användare som försöker boka det senaste tillgängliga rummet kan du överväga att lägga till användare i en väntelista eller föreslå alternativ. Asynkron bearbetning Bearbeta kommandon asynkront genom att placera dem i en kö i stället för att hantera dem synkront. Förstå frågor. Frågor ändrar aldrig data. I stället returnerar de dataöverföringsobjekt (DTU:er) som presenterar nödvändiga data i ett bekvämt format, utan någon domänlogik. Denna distinkta ansvarsfördelning förenklar systemets utformning och implementering.
Separera läsmodeller och skrivmodeller
Om du separerar läsmodellen från skrivmodellen förenklas systemdesignen och implementeringen genom att specifika problem för dataskrivningar och dataläsningar åtgärdas. Den här separationen förbättrar tydligheten, skalbarheten och prestandan, men medför kompromisser. Exempelvis kan autogenereringsverktyg som O/RM-ramverk (object-relational mapping) inte automatiskt generera CQRS-kod från ett databasschema, så du behöver anpassad logik för att överbrygga klyftan.
I följande avsnitt beskrivs två primära metoder för att implementera läsmodell och skrivningsmodellseparation i CQRS. Varje metod har unika fördelar och utmaningar, till exempel synkronisering och konsekvenshantering.
Avgränsa modeller i ett enda datalager
Den här metoden representerar den grundläggande nivån för CQRS, där både läs- och skrivmodellerna delar en enda underliggande databas men upprätthåller en distinkt logik för sina åtgärder. Med en grundläggande CQRS-arkitektur kan du avgränsa skrivmodellen från läsmodellen samtidigt som du förlitar dig på ett delat datalager.
Den här metoden förbättrar tydlighet, prestanda och skalbarhet genom att definiera distinkta modeller för hantering av läs- och skrivproblem.
En skrivmodell är utformad för att hantera kommandon som uppdaterar eller bevarar data. Den innehåller validerings- och domänlogik och hjälper till att säkerställa datakonsekvens genom att optimera för transaktionsintegritet och affärsprocesser.
En läsmodell är utformad för att hantera frågor för att hämta data. Den fokuserar på att generera DTU:er eller projektioner som är optimerade för presentationsskiktet. Det förbättrar frågeprestanda och svarstider genom att undvika domänlogik.
Separata modeller i olika datalager
En mer avancerad CQRS-implementering använder distinkta datalager för läs- och skrivmodellerna. Genom att separera läs- och skrivdatalager kan du skala varje modell så att den matchar belastningen. Du kan också använda en annan lagringsteknik för varje datalager. Du kan använda en dokumentdatabas för läsdatalagret och en relationsdatabas för skrivdatalagret.
När du använder separata datalager måste du se till att båda förblir synkroniserade. Ett vanligt mönster är att skriva modellen publicerar händelser när den uppdaterar databasen, som läsmodellen använder för att uppdatera sina data. Mer information om hur du använder händelser finns i Händelsedriven arkitektur. Eftersom du vanligtvis inte kan registrera meddelandeförmedlare och databaser i en enda distribuerad transaktion kan utmaningar med konsistens uppstå när du uppdaterar databasen och publicerar händelser. Mer information finns i Idempotent meddelandebearbetning.
Läsdatalagret kan använda ett eget dataschema som är optimerat för frågor. Den kan till exempel lagra en materialiserad vy av data för att undvika komplexa kopplingar eller O/RM-mappningar. Läsdatalagret kan vara en skrivskyddad replik av skrivlagret eller ha en annan struktur. Om du distribuerar flera skrivskyddade repliker kan du förbättra prestandan genom att minska svarstiden och öka tillgängligheten, särskilt i distribuerade scenarier.
Fördelar med CQRS
Oberoende skalning. Med CQRS kan läsmodeller och skrivmodeller skalas separat. Den här metoden kan bidra till att minimera låskonflikter och förbättra systemets prestanda under belastning.
Optimerade datascheman. Läsåtgärder kan använda ett schema som är optimerat för frågor. Skrivåtgärder använder ett schema som är optimerat för uppdateringar.
Säkerhet. Genom att separera läsningar och skrivningar kan du se till att endast lämpliga domänentiteter eller åtgärder har behörighet att utföra skrivåtgärder på data.
Avgränsning av problem. Att separera läs- och skrivansvaret resulterar i renare och mer underhållsbara modeller. Skrivsidan hanterar vanligtvis komplex affärslogik. Lässidan kan förbli enkel och fokusera på frågeeffektivitet.
Enklare frågor. När du lagrar en materialiserad vy i läsdatabasen kan programmet undvika komplexa kopplingar när det frågar.
Problem och överväganden
Tänk på följande när du bestämmer hur du ska implementera det här mönstret:
Ökad komplexitet. Kärnkonceptet för CQRS är enkelt, men det kan medföra betydande komplexitet i programdesignen, särskilt när det kombineras med händelsekällornas mönster.
Meddelandeutmaningar. Meddelanden är inte ett krav för CQRS, men du använder det ofta för att bearbeta kommandon och publicera uppdateringshändelser. När meddelanden ingår måste systemet ta hänsyn till potentiella problem, till exempel meddelandefel, dubbletter och återförsök. Mer information om strategier för att hantera kommandon som har olika prioriteringar finns i Prioritetsköer.
Eventuell konsistens. När läsdatabaserna och skrivdatabaserna separeras kanske inte läsdata visar de senaste ändringarna omedelbart. Den här fördröjningen resulterar i inaktuella data. Det kan vara svårt att se till att läsmodellarkivet förblir up-todatum med ändringar i skrivmodellarkivet. Dessutom kräver identifiering och hantering av scenarier där en användare agerar på inaktuella data noggrant övervägande.
När du ska använda det här mönstret
Använd det här mönstret i sådana här scenarier:
Du arbetar i samarbetsmiljöer. I miljöer där flera användare får åtkomst till och ändrar samma data samtidigt hjälper CQRS till att minska sammanslagningskonflikter. Kommandon kan innehålla tillräckligt med kornighet för att förhindra konflikter, och systemet kan lösa eventuella konflikter som inträffar inom kommandologiken.
Du har uppgiftsbaserade användargränssnitt. Program som vägleder användare genom komplexa processer som en serie steg eller med komplexa domänmodeller drar nytta av CQRS.
Skrivmodellen har en fullständig kommandobearbetningsstack med affärslogik, indataverifiering och affärsverifiering. Skrivmodellen kan behandla en uppsättning associerade objekt som en enda enhet för dataändringar, vilket kallas aggregering i domändriven designterminologi. Skrivmodellen kan också hjälpa till att säkerställa att dessa objekt alltid är i ett konsekvent tillstånd.
Läsmodellen har ingen affärslogik eller valideringsstack. Den returnerar ett DTO-objekt för användning i en visningsmodell. Läsningsmodellen är i slutlig överensstämmelse med skrivningsmodellen.
Du behöver prestandajustering. System där prestanda för dataläsningar måste finjusteras separat från prestanda för dataskrivningar drar nytta av CQRS. Det här mönstret är särskilt fördelaktigt när antalet läsningar är större än antalet skrivningar. Läsmodellen skalas vågrätt för att hantera stora frågevolymer. Skrivprocessmodellen används på färre instanser för att minimera sammanslagningskonflikter och upprätthålla konsekventitet.
Du har separation av utvecklingsområden. CQRS gör det möjligt för team att arbeta oberoende av varandra. Ett team implementerar den komplexa affärslogik i skrivmodellen, och ett annat team utvecklar komponenterna för läsmodellen och användargränssnittet.
Du har föränderliga system. CQRS stöder system som utvecklas över tid. Den rymmer nya modellversioner, frekventa ändringar av affärsregler eller andra ändringar utan att påverka befintliga funktioner.
Du behöver systemintegrering: System som integreras med andra undersystem, särskilt system som använder mönstret Händelsekällor, förblir tillgängliga även om ett undersystem tillfälligt misslyckas. CQRS isolerar fel, vilket förhindrar att en enskild komponent påverkar hela systemet.
Det här mönstret kanske inte är lämpligt när:
Domänen eller affärsreglerna är enkla.
Det räcker med ett enkelt CRUD-gränssnitt och dataåtkomståtgärder.
Design av arbetsbelastning
Utvärdera hur du använder CQRS-mönstret i en arbetsbelastnings design för att hantera de mål och principer som beskrivs i Grundpelarna i Azure Well-Architected Framework. Följande tabell innehåller vägledning om hur det här mönstret stöder målen för pelaren Prestandaeffektivitet.
Grundpelare | Så här stöder det här mönstret pelarmål |
---|---|
Prestandaeffektivitet hjälper din arbetsbelastning att effektivt uppfylla kraven genom optimeringar inom skalning, data och kod. | Separationen av läsåtgärder och skrivåtgärder i hög läs-till-skriv-arbetsbelastningar möjliggör riktade prestanda- och skaloptimeringar för varje åtgärds specifika syfte. - PE:05 Skalning och partitionering - PE:08 Dataprestanda |
Överväg eventuella kompromisser mot målen för de andra pelarna som det här mönstret kan införa.
Kombinera Event Sourcing och CQRS-mönster
Vissa implementeringar av CQRS innehåller Event Sourcing-mönstret. Det här mönstret lagrar systemets tillstånd som en kronologisk serie händelser. Varje händelse registrerar de ändringar som görs i data vid en viss tidpunkt. För att fastställa det aktuella tillståndet spelar systemet upp dessa händelser i ordning. I den här konfigurationen:
Händelsearkivet är den skrivmodellen och den enda sanningskällan.
Den läsmodellen genererar materialiserade vyer från dessa händelser, vanligtvis i ett mycket avnormaliserat format. Dessa vyer optimerar datahämtningen genom att skräddarsy strukturer för att fråga efter och visa krav.
Fördelar med att kombinera Event Sourcing och CQRS-mönstret
Samma händelser som uppdaterar skrivmodellen kan fungera som indata till läsmodellen. Läsmodellen kan sedan skapa en ögonblicksbild i realtid av det aktuella tillståndet. Dessa ögonblicksbilder optimerar frågor genom att tillhandahålla effektiva och förberäknade vyer av data.
I stället för att lagra det aktuella tillståndet direkt använder systemet en ström av händelser som skrivarkiv. Den här metoden minskar uppdateringskonflikter i aggregeringar och förbättrar prestanda och skalbarhet. Systemet kan bearbeta dessa händelser asynkront för att skapa eller uppdatera materialiserade vyer för läsdatalagret.
Eftersom händelsearkivet fungerar som en enda sanningskälla kan du enkelt återskapa materialiserade vyer eller anpassa dig till ändringar i läsmodellen genom att spela upp historiska händelser igen. I grund och botten fungerar materialiserade vyer som en beständig, skrivskyddad cache som är optimerad för snabba och effektiva frågor.
Överväganden för att kombinera Event Sourcing och CQRS-mönstren
Innan du kombinerar CQRS-mönstret med mönstret händelsekällorbör du utvärdera följande överväganden:
Slutlig konsekvens: Eftersom datalagren för skrivning och läsning är separata kan uppdateringar av läsdatalagret ligga efter händelsegenereringen. Den här fördröjningen resulterar i eventuell konsistens.
Ökad komplexitet: Att kombinera CQRS-mönstret med händelsekällor kräver en annan designmetod, vilket kan göra en lyckad implementering mer utmanande. Du måste skriva kod för att generera, bearbeta och hantera händelser och sammanställa eller uppdatera vyer för läsmodellen. Mönstret Händelsekällor förenklar dock domänmodellering och gör att du enkelt kan återskapa eller skapa nya vyer genom att bevara historiken och avsikten för alla dataändringar.
Prestanda för visningsgenerering: Generera materialiserade vyer för läsmodellen kan förbruka betydande tid och resurser. Detsamma gäller för projektering av data genom att spela upp och bearbeta händelser för specifika entiteter eller samlingar. Komplexiteten ökar när beräkningar omfattar analys eller summering av värden under långa perioder eftersom alla relaterade händelser måste undersökas. Implementera ögonblicksbilder av data med jämna mellanrum. Lagra till exempel det aktuella tillståndet för en entitet eller periodiska ögonblicksbilder av aggregerade summor, vilket är antalet gånger en specifik åtgärd inträffar. Ögonblicksbilder minskar behovet av att bearbeta den fullständiga händelsehistoriken upprepade gånger, vilket förbättrar prestandan.
Exempel
Följande kod visar utdrag från ett exempel på en CQRS-implementering som använder olika definitioner för läsmodellerna och skrivmodellerna. Modellgränssnitten dikterar inte funktionerna i underliggande datalager, och de kan utvecklas och finjusteras oberoende av varandra eftersom dessa gränssnitt är separata.
Följande kod visar läsningsmodellens definition.
// 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; }
}
}
Systemet tillåter användare att betygsätta produkter. Programkoden gör detta med hjälp av kommandot RateProduct
som visas i följande kod.
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; }
}
Systemet använder ProductsCommandHandler
klassen för att hantera kommandon som programmet skickar. Klienter skickar vanligtvis kommandon till domänen via ett meddelandesystem, till exempel en kö. Kommandohanteraren accepterar dessa kommandon och anropar metoder i domängränssnittet. Granulariteten för varje kommando har utformats för att minska risken för begäranden som står i konflikt. Följande kod visar en översikt över klassen 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)
{
...
}
}
Nästa steg
Följande information kan vara relevant när du implementerar det här mönstret:
- Vägledning för datapartitionering beskriver metodtips för hur du delar in data i partitioner som du kan hantera och komma åt separat för att förbättra skalbarheten, minska konkurrensen och optimera prestanda.
Relaterade resurser
Händelsekällmönster. Det här mönstret beskriver hur du förenklar uppgifter i komplexa domäner och förbättrar prestanda, skalbarhet och svarstider. Den förklarar också hur man skapar konsekvens i transaktionsdata samtidigt som fullständiga revisionsloggar och historik bibehålls, som möjliggör kompenserande åtgärder.
Mönster för materialiserad vy. Det här mönstret skapar förifyllda vyer, så kallade materialiserade vyer, för effektiv frågekörning och extrahering av data från ett eller flera datalager. Läsningsmodellen i en CQRS-implementering kan innehålla materialiserade vyer av skrivningsmodelldata, eller läsningsmodellen kan användas för att skapa materialiserade vyer.