Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
La séparation des responsabilités des requêtes de commande (CQRS) est un modèle de conception qui sépare les opérations de lecture et d’écriture d’un magasin de données dans des modèles de données distincts. Cette approche permet à chaque modèle d’être optimisé indépendamment et peut améliorer les performances, la scalabilité et la sécurité d’une application.
Contexte et problème
Dans une architecture traditionnelle, un modèle de données unique est souvent utilisé pour les opérations de lecture et d’écriture. Cette approche est simple et convient aux opérations CRUD (Create, Read, Update et Delete) de base.
À mesure que les applications augmentent, il peut devenir de plus en plus difficile d’optimiser les opérations de lecture et d’écriture sur un modèle de données unique. Les opérations de lecture et d’écriture ont souvent des exigences de performances et de mise à l’échelle différentes. Une architecture CRUD traditionnelle ne prend pas en compte cette asymétrie, ce qui peut entraîner les défis suivants :
incompatibilité des données : Les représentations de lecture et d’écriture des données diffèrent souvent. Certains champs requis pendant les mises à jour peuvent être inutiles pendant les opérations de lecture.
Contention de verrouillage : les opérations parallèles sur le même ensemble de données peuvent provoquer une contention de verrouillage.
Problèmes de performances : L’approche traditionnelle peut avoir un effet négatif sur les performances en raison de la charge sur le magasin de données et la couche d’accès aux données, ainsi que sur la complexité des requêtes requises pour récupérer des informations.
Défis de sécurité : Il peut être difficile de gérer la sécurité lorsque les entités sont soumises à des opérations de lecture et d’écriture. Ce chevauchement peut exposer des données dans des contextes inattendus.
La combinaison de ces responsabilités peut entraîner un modèle trop compliqué.
Solution
Utilisez le modèle CQRS pour séparer les opérations d’écriture ou les commandes, des opérations de lecture ou des requêtes. Les commandes mettent à jour les données. Les requêtes récupèrent des données. Le modèle CQRS est utile dans les scénarios qui nécessitent une séparation claire entre les commandes et les lectures.
Comprendre les commandes. Les commandes doivent représenter des tâches métier spécifiques au lieu de mises à jour de données de bas niveau. Par exemple, dans une application de réservation d’hôtel, utilisez la commande « Réserver une chambre d’hôtel » au lieu de « Définir ReservationStatus sur Réservé ». Cette approche capture mieux l’intention de l’utilisateur et aligne les commandes avec les processus métier. Pour vous assurer que les commandes réussissent, vous devrez peut-être affiner le flux d’interaction utilisateur et la logique côté serveur et envisager un traitement asynchrone.
Zone de raffinement Recommandation Validation côté client Validez des conditions spécifiques avant d’envoyer la commande pour éviter les échecs évidents. Par exemple, si aucune salle n’est disponible, désactivez le bouton « Réserver » et fournissez un message clair et convivial dans l’interface utilisateur qui explique pourquoi la réservation n’est pas possible. Cette configuration réduit les demandes de serveur inutiles et fournit des commentaires immédiats aux utilisateurs, ce qui améliore leur expérience. Logique côté serveur Améliorez la logique métier pour gérer correctement les cas de périphérie et les défaillances. Par exemple, pour résoudre les conditions de concurrence, telles que le cas où plusieurs utilisateurs tentent de réserver la dernière chambre disponible, envisagez d’ajouter les utilisateurs à une liste d’attente ou de suggérer des alternatives. Traitement asynchrone Traiter les commandes de manière asynchrone en les plaçant dans une file d’attente, au lieu de les gérer de manière synchrone. Comprendre les requêtes. Les requêtes ne modifient jamais les données. Au lieu de cela, ils retournent des objets de transfert de données (DTO) qui présentent les données requises dans un format pratique, sans logique de domaine. Cette séparation distincte des responsabilités simplifie la conception et l’implémentation du système.
Séparer les modèles de lecture et les modèles d’écriture
La séparation du modèle de lecture du modèle d’écriture simplifie la conception et l’implémentation du système en répondant à des préoccupations spécifiques pour les écritures de données et les lectures de données. Cette séparation améliore la clarté, la scalabilité et les performances, mais introduit des compromis. Par exemple, les outils de génération de modèles automatique comme les frameworks de mappage relationnel objet (O/RM) ne peuvent pas générer automatiquement du code CQRS à partir d’un schéma de base de données. Vous avez donc besoin d’une logique personnalisée pour combler l’écart.
Les sections suivantes décrivent deux approches principales pour implémenter la séparation du modèle de lecture et du modèle d’écriture dans CQRS. Chaque approche présente des avantages et des défis uniques, tels que la synchronisation et la gestion de la cohérence.
Séparer les modèles dans un stockage de données unique
Cette approche représente le niveau fondamental de CQRS, où les modèles de lecture et d’écriture partagent une base de données sous-jacente unique, mais conservent une logique distincte pour leurs opérations. Une architecture CQRS de base vous permet de délimiter le modèle d’écriture à partir du modèle de lecture tout en s’appuyant sur un magasin de données partagé.
Cette approche améliore la clarté, les performances et l’évolutivité en définissant des modèles distincts pour gérer les problèmes de lecture et d’écriture.
Un modèle d’écriture est conçu pour gérer les commandes qui mettent à jour ou conservent des données. Il inclut la logique de validation et de domaine, et permet de garantir la cohérence des données en optimisant l’intégrité transactionnelle et les processus métier.
Un modèle de lecture est conçu pour traiter des requêtes pour récupérer des données. Il se concentre sur la génération de DTO ou de projections optimisées pour la couche de présentation. Elle améliore les performances et la réactivité des requêtes en évitant la logique de domaine.
Modèles distincts dans différents magasins de données
Une implémentation CQRS plus avancée utilise des magasins de données distincts pour les modèles de lecture et d’écriture. La séparation des magasins de données de lecture et d’écriture vous permet d'adapter chaque modèle à la charge. Il vous permet également d’utiliser une technologie de stockage différente pour chaque magasin de données. Vous pouvez utiliser une base de données de documents pour le magasin de données de lecture et une base de données relationnelle pour le magasin de données d’écriture.
Lorsque vous utilisez des magasins de données distincts, vous devez vous assurer que les deux restent synchronisés. Un modèle courant consiste à faire en sorte que le modèle d’écriture publie des événements lorsqu’il met à jour la base de données, que le modèle de lecture utilise pour actualiser ses données. Pour plus d'informations sur l'utilisation d'événements, consultez le style d'architecture à base d'événements. Étant donné que vous ne pouvez généralement pas inscrire les répartiteurs de messages et les bases de données dans une transaction distribuée unique, les défis de cohérence peuvent se produire lorsque vous mettez à jour la base de données et les événements de publication. Pour plus d'informations, reportez-vous à la section Traitement des messages idempotents.
Le magasin de données en lecture peut utiliser son propre schéma de données optimisé pour les requêtes. Par exemple, il peut stocker une vue matérialisée des données pour éviter les jointures complexes ou les mappages O/RM. Le stockage de lecture peut être une réplique en lecture seule du stockage d'écriture ou avoir une structure différente. Le déploiement de plusieurs réplicas en lecture seule peut améliorer les performances en réduisant la latence et en augmentant la disponibilité, en particulier dans les scénarios distribués.
Avantages de CQRS
Mise à l'échelle indépendante. CQRS permet aux modèles de lecture et d'écriture d'évoluer indépendamment. Cette approche peut aider à réduire la contention des verrous et à améliorer les performances du système sous charge.
Schémas de données optimisés. Les opérations de lecture peuvent utiliser un schéma optimisé pour les requêtes. Les opérations d’écriture utilisent un schéma optimisé pour les mises à jour.
Sécurité. En séparant les lectures et les écritures, vous pouvez vous assurer que seules les entités de domaine ou opérations appropriées sont autorisées à effectuer des actions d’écriture sur les données.
Séparation des problèmes. La séparation des responsabilités de lecture et d’écriture entraîne des modèles plus propres et plus gérables. Le côté écriture gère généralement une logique métier complexe. Le côté lecture peut rester simple et axé sur l’efficacité des requêtes.
Requêtes plus simples. Lorsque vous stockez une vue matérialisée dans la base de données de lecture, l’application peut éviter les jointures complexes lorsqu’elle interroge.
Problèmes et considérations
Tenez compte des points suivants lorsque vous décidez comment implémenter ce modèle :
Complexité accrue. Le concept de base de CQRS est simple, mais il peut introduire une complexité significative dans la conception de l’application, en particulier lorsqu’il est combiné au modèle d’approvisionnement en événements.
Défis liés à la messagerie. La messagerie n’est pas requise pour CQRS, mais vous l’utilisez souvent pour traiter les commandes et publier des événements de mise à jour. Lorsque la messagerie est incluse, le système doit tenir compte des problèmes potentiels tels que les échecs de message, les doublons et les nouvelles tentatives. Pour plus d’informations sur les stratégies de gestion des commandes qui ont des priorités variables, consultez Files d’attente prioritaires.
Cohérence éventuelle. Lorsque les bases de données de lecture et les bases de données d’écriture sont séparées, les données de lecture peuvent ne pas afficher immédiatement les modifications les plus récentes. Ce délai entraîne des données obsolètes. Il peut être difficile de garantir que le magasin de modèles de lecture reste à jour avec les modifications apportées au magasin de modèles d'écriture. En outre, la détection et la gestion des scénarios où un utilisateur agit sur des données obsolètes nécessite une considération minutieuse.
Quand utiliser ce modèle
Utilisez ce modèle dans les situations suivantes :
Vous travaillez dans des environnements collaboratifs. Dans les environnements où plusieurs utilisateurs accèdent et modifient simultanément les mêmes données, CQRS permet de réduire les conflits de fusion. Les commandes peuvent inclure suffisamment de granularité pour éviter les conflits, et le système peut résoudre les conflits qui se produisent dans la logique de commande.
Vous disposez d’interfaces utilisateur basées sur des tâches. Les applications qui guident les utilisateurs via des processus complexes en tant que série d’étapes ou avec des modèles de domaine complexes bénéficient de CQRS.
Le modèle d’écriture a une pile de traitement de commandes complète avec une logique métier, une validation d’entrée et une validation métier. Le modèle d’écriture peut traiter un ensemble d’objets associés comme une unité unique pour les modifications de données, appelée agrégat dans la terminologie de conception pilotée par le domaine. Le modèle d’écriture peut également vous aider à garantir que ces objets sont toujours dans un état cohérent.
Le modèle de lecture n’a pas de logique métier ni de pile de validation. Il renvoie un DTO à utiliser dans un modèle de vue. Le modèle de lecture est finalement cohérent avec le modèle d’écriture.
Vous avez besoin d’un réglage des performances. Les systèmes dans lesquels les performances de lecture de données doivent être affinées séparément des performances d'écriture de données bénéficient du CQRS. Ce modèle est particulièrement bénéfique lorsque le nombre de lectures est supérieur au nombre d’écritures. Le modèle de lecture évolue horizontalement pour gérer de gros volumes de requêtes. Le modèle d’écriture s’exécute sur moins d’instances pour réduire les conflits de fusion et maintenir la cohérence.
Vous avez une séparation des préoccupations de développement. CQRS permet aux équipes de travailler indépendamment. Une équipe implémente la logique métier complexe dans le modèle d’écriture, et une autre équipe développe les composants du modèle de lecture et de l’interface utilisateur.
Vous avez des systèmes en évolution. CQRS prend en charge les systèmes qui évoluent au fil du temps. Il s’adapte aux nouvelles versions de modèle, aux modifications fréquentes apportées aux règles d’entreprise ou à d’autres modifications sans affecter les fonctionnalités existantes.
Vous avez besoin de l’intégration du système : Les systèmes qui s’intègrent à d’autres sous-systèmes, en particulier les systèmes qui utilisent le modèle d’approvisionnement en événements, restent disponibles même si un sous-système échoue temporairement. CQRS isole les défaillances, ce qui empêche un composant unique d’affecter l’ensemble du système.
Ce modèle peut ne pas convenir lorsque :
Le domaine ou les règles d’entreprise sont simples.
Une interface utilisateur simple de style CRUD et les opérations d’accès aux données sont suffisantes.
Conception de la charge de travail
Évaluez comment utiliser le modèle CQRS dans la conception d’une charge de travail pour répondre aux objectifs et principes abordés dans les piliers Azure Well-Architected Framework. Le tableau suivant fournit des conseils sur la façon dont ce modèle prend en charge les objectifs du pilier Efficacité des performances.
Pilier | Comment ce modèle soutient les objectifs des piliers. |
---|---|
L’efficacité des performances permet à votre charge de travail de répondre efficacement aux demandes par le biais d’optimisations de la mise à l’échelle, des données et du code. | La séparation des opérations de lecture et d’écriture dans des charges de travail de lecture à écriture élevées permet d’optimiser les performances et la mise à l’échelle ciblées pour chaque opération. - PE :05 Mise à l’échelle et partitionnement - PE :08 Performance des données |
Considérez tous les compromis contre les objectifs des autres piliers que ce modèle pourrait introduire.
Combiner les modèles Event Sourcing et CQRS
Certaines implémentations de CQRS incorporent le modèle d’approvisionnement en événements. Ce modèle stocke l’état du système sous la forme d’une série chronologique d’événements. Chaque événement capture les modifications apportées aux données à un moment spécifique. Pour déterminer l’état actuel, le système relit ces événements dans l’ordre. Dans cette configuration :
Le magasin d'événements est le modèle d'écriture et la source unique de vérité.
Le modèle de lecture génère des vues matérialisées à partir de ces événements, généralement sous une forme hautement dénormalisée. Ces vues optimisent la récupération des données en personnalisant les structures pour interroger et répondre aux besoins d'affichage.
Avantages de la combinaison des modèles d’approvisionnement en événements et de CQRS
Les mêmes événements que ceux qui mettent à jour le modèle d’écriture peuvent servir d’entrées au modèle de lecture. Le modèle de lecture peut ensuite générer un instantané en temps réel de l’état actuel. Ces instantanés optimisent les requêtes en fournissant des vues efficaces et précomputées des données.
Au lieu de stocker directement l’état actuel, le système utilise un flux d’événements comme magasin d’écriture. Cette approche réduit les conflits de mise à jour sur les agrégats et améliore les performances et l’extensibilité. Le système peut traiter ces événements de manière asynchrone pour générer ou mettre à jour des vues matérialisées pour le magasin de données en lecture.
Étant donné que le magasin d’événements agit comme source unique de vérité, vous pouvez facilement régénérer des vues matérialisées ou s’adapter aux modifications du modèle de lecture en relectant les événements historiques. En fait, les vues matérialisées fonctionnent comme un cache durable en lecture seule optimisé pour les requêtes rapides et efficaces.
Considérations relatives à la combinaison des modèles d’approvisionnement en événements et de CQRS
Avant de combiner le modèle CQRS avec le modèle d’approvisionnement en événements , évaluez les considérations suivantes :
Cohérence éventuelle : Étant donné que les magasins de données d’écriture et de lecture sont distincts, les mises à jour du magasin de données de lecture peuvent être en retard par rapport à la génération d'événements. Ce retard aboutit à une cohérence éventuelle.
Complexité accrue : La combinaison du modèle CQRS avec le modèle d’approvisionnement en événements nécessite une approche de conception différente, ce qui peut rendre une implémentation réussie plus difficile. Vous devez écrire du code pour générer, traiter et gérer des événements, et assembler ou mettre à jour des vues pour le modèle de lecture. Toutefois, le modèle d’approvisionnement en événements simplifie la modélisation de domaine et vous permet de reconstruire ou de créer facilement de nouvelles vues en préservant l’historique et l’intention de toutes les modifications de données.
Performance de génération d’affichage : La génération de vues matérialisées pour le modèle de consultation peut consommer beaucoup de temps et de ressources. Il en va de même pour projeter des données en relectant et en traitant des événements pour des entités ou des collections spécifiques. La complexité augmente lorsque les calculs impliquent l’analyse ou la somme de valeurs sur de longues périodes, car tous les événements connexes doivent être examinés. Implémentez des instantanés des données à intervalles réguliers. Par exemple, stockez l’état actuel d’une entité ou prenez des instantanés périodiques des totaux agrégés, qui correspondent au nombre de fois où une action spécifique se produit. Les instantanés réduisent la nécessité de traiter l'intégralité de l'historique des événements de manière répétée, ce qui améliore les performances.
Exemple :
Le code suivant montre des extraits d’un exemple d’implémentation CQRS qui utilise différentes définitions pour les modèles de lecture et les modèles d’écriture. Les interfaces de modèle ne dictent pas les fonctionnalités des magasins de données sous-jacents, et elles peuvent évoluer et être affinées indépendamment, car ces interfaces sont distinctes.
Le code suivant montre la définition du modèle de lecture.
// 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; }
}
}
Le système permet aux utilisateurs d’évaluer des produits. Le code de l’application effectue cette opération à l’aide de la RateProduct
commande indiquée dans le code suivant.
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; }
}
Le système utilise la ProductsCommandHandler
classe pour gérer les commandes envoyées par l’application. En règle générale, les clients envoient des commandes au domaine via un système de messagerie comme une file d’attente. Le gestionnaire de commandes accepte ces commandes et appelle les méthodes de l’interface du domaine. La granularité de chaque commande est conçue pour réduire le risque de requêtes en conflit. Le code suivant montre une description de la classe 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)
{
...
}
}
Étape suivante
Les informations suivantes peuvent être pertinentes lorsque vous implémentez ce modèle :
- Les conseils de partitionnement des données décrivent les meilleures pratiques pour diviser les données en partitions que vous pouvez gérer et accéder séparément pour améliorer la scalabilité, réduire la contention et optimiser les performances.
Ressources associées
Modèle d'approvisionnement en événements. Ce modèle décrit comment simplifier les tâches dans des domaines complexes et améliorer les performances, l’extensibilité et la réactivité. Il explique également comment fournir une cohérence pour les données transactionnelles tout en conservant des pistes d’audit complètes et l’historique qui peuvent activer les actions de compensation.
Modèle de vue matérialisée. Ce modèle crée des vues préremplies, appelées vues matérialisées, pour une interrogation efficace et l’extraction de données à partir d’un ou plusieurs magasins de données. Le mode de lecture d’une implémentation CQRS peut contenir des vues matérialisées des données du modèle d’écriture, ou le modèle de lecture peut être utilisé pour générer des vues matérialisées.