System Design

Maîtriser le CQRS : Découpler les lectures et les écritures pour une conception de système évolutive

Dans le domaine de la conception de systèmes distribués, le modèle CRUD (Create, Read, Update, Delete) traditionnel sert souvent de point de départ. Cependant, à mesure que les applications deviennent plus complexes et volumineuses, une couche de base de données monolithique peut devenir un goulot d'étranglement. Voici le CQRS (Command Query Responsibility Segregation, ou Ségrégation des responsabilités de commande et de requête). Ce modèle architectural, popularisé par Greg Young et Eric Evans, offre une solution robuste pour séparer les préoccupations liées à la lecture et à l'écriture des données, permettant aux systèmes de monter en charge indépendamment et d'être optimisés pour des opérations spécifiques.

Le concept fondamental : Pourquoi séparer ?

Au cœur du CQRS repose un principe simple : les commandes et les requêtes ont des caractéristiques différentes. Une Commande modifie l'état du système (par exemple, passer une commande), tandis qu'une Requête récupère des informations sans effets secondaires (par exemple, consulter l'historique des commandes). Dans une base de données relationnelle standard, ces opérations entrent souvent en concurrence pour les mêmes ressources, ce qui entraîne des conflits et des problèmes de performance.

En ségrégeant ces opérations, le CQRS vous permet d'utiliser des modèles de données différents pour la lecture et l'écriture. Vous pouvez disposer d'une base de données relationnelle hautement normalisée et conforme aux propriétés ACID pour gérer les transactions (le Modèle d'Écriture) et d'une base de données NoSQL ou d'un entrepôt de données dénormalisés et optimisés pour la lecture afin d'afficher des rapports (le Modèle de Lecture).

Principaux avantages du CQRS

Adopter le CQRS n'est pas une solution miracle, mais il offre des avantages distincts dans des scénarios spécifiques :

  1. Évolutivité : Vous pouvez faire monter en charge vos réplicas en lecture et vos serveurs en écriture indépendamment, en fonction de la charge.
  2. Sécurité : Le contrôle d'accès peut être plus granulaire. Un service peut avoir la permission de mettre à jour les profils utilisateurs mais pas de les supprimer.
  3. Simplicité : Le modèle d'écriture peut se concentrer strictement sur la logique métier et l'intégrité, tandis que le modèle de lecture se concentre purement sur la présentation et la performance.
  4. Audibilité : Le CQRS s'associe naturellement à l'Event Sourcing, où chaque changement est enregistré sous forme d'événement immuable, fournissant une piste d'audit complète.

Mise en œuvre du CQRS : Un exemple pratique

Examinons à quoi cela pourrait ressembler dans une architecture de service typique de style .NET ou Java. Remarquez comment l'interface distingue clairement entre les commandes et les requêtes.

// L'interface de Commande (Modèle d'Écriture)
public interface ICommand {
    Guid Id();
}

public class PlaceOrderCommand : ICommand {
    private readonly Guid _orderId;
    private readonly List<OrderItem> _items;

    public PlaceOrderCommand(Guid orderId, List<OrderItem> items) {
        _orderId = orderId;
        _items = items;
    }

    public Guid Id() { return _orderId; }
}

// L'interface de Requête (Modèle de Lecture)
public interface IQuery<TResult> {
}

public class GetOrderDetailsQuery : IQuery<OrderDetailsDto> {
    private readonly Guid _orderId;

    public GetOrderDetailsQuery(Guid orderId) {
        _orderId = orderId;
    }
}

// L'implémentation du Gestionnaire
public class OrderHandler {
    // Gère les changements d'état
    public void Handle(PlaceOrderCommand command) {
        var order = new Order(command.Id(), command.Items());
        // Persistance dans la base de données d'écriture
        _repository.Save(order);
        
        // Publication de l'événement pour mettre à jour le modèle de lecture
        _eventPublisher.Publish(new OrderPlacedEvent(command.Id()));
    }

    // Gère la récupération des données
    public OrderDetailsDto Handle(GetOrderDetailsQuery query) {
        // Requête depuis la base de données de lecture (vue optimisée)
        return _readRepository.GetOrderDetails(query.OrderId());
    }
}

Dans cet exemple, la méthode Handle pour la commande interagit avec la source de vérité, persiste le changement et déclenche un événement. Cet événement peut ensuite être consommé par un processus distinct qui met à jour la base de données de lecture dénormalisée, garantissant que les données disponibles pour les requêtes sont cohérentes à terme (eventual consistency).

Conclusion

Le CQRS est un modèle puissant qui permet des architectures performantes et évolutives en reconnaissant que les lectures et les écritures sont fondamentalement des opérations différentes. Bien qu'il introduise une complexité concernant la cohérence des données et la gestion de l'infrastructure, les avantages pour les applications à grande échelle et intensives en données sont indéniables. Pour les développeurs visant à construire des systèmes résilients capables de gérer un débit massif et une logique métier complexe, la maîtrise du CQRS est une étape essentielle dans leur parcours architectural.

Share: