CQRS et Event Sourcing en Go : architecture pour les systèmes complexes

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Quand le CRUD classique ne suffit plus : introduction à CQRS et Event Sourcing

\n

Imaginez que vous construisez un système de réservation de billets. Tout est simple tant qu'il y a dix utilisateurs. Mais quand ils deviennent mille, puis un million, les problèmes commencent : les bases de données se bloquent, les requêtes entrent en conflit, et l'historique des modifications est impossible à reconstituer. Une situation familière ?

\n

Dans le monde du développement Go, où la performance et le parallélisme sont primordiaux, deux patrons architecturaux aident à résoudre ces problèmes : CQRS (Command Query Responsibility Segregation) et Event Sourcing. Aujourd'hui, nous les analysons en pratique — avec du code, des exemples et des scénarios d'utilisation réels.

\n\n

Qu'est-ce que CQRS en termes simples ?

\n

CQRS est la séparation des responsabilités entre les commandes (écriture) et les requêtes (lecture). Dans le CRUD classique, un seul modèle de données gère à la fois la création et la lecture. Avec CQRS, nous créons deux modèles distincts :

\n
    \n
  • Modèle de commande — responsable de la modification de l'état (création, mise à jour, suppression)
  • \n
  • Modèle de requête — responsable uniquement de la lecture des données
  • \n
\n

Cela permet d'optimiser chaque côté indépendamment : l'écriture peut être strictement validée, tandis que la lecture peut être dénormalisée et rapide. En Go, cela est particulièrement efficace grâce aux goroutines et aux canaux.

\n\n

Exemple simple de CQRS en Go

\n

Créons un système de base pour la gestion des commandes. Commençons par les commandes :

\n\n
type CreateOrderCommand struct {\n    OrderID   string\n    UserID    string\n    ProductID string\n    Amount    float64\n}\n\ntype CommandHandler struct {\n    repo *OrderRepository\n}\n\nfunc (h *CommandHandler) HandleCreateOrder(ctx context.Context, cmd CreateOrderCommand) error {\n    order := Order{\n        ID:        cmd.OrderID,\n        UserID:    cmd.UserID,\n        ProductID: cmd.ProductID,\n        Amount:    cmd.Amount,\n        Status:    "pending",\n        CreatedAt: time.Now(),\n    }\n    return h.repo.Save(ctx, order)\n}
\n\n

Créons maintenant un modèle séparé pour la lecture :

\n\n
type OrderQuery struct {\n    repo *ReadRepository\n}\n\ntype OrderView struct {\n    OrderID    string\n    UserName   string\n    ProductName string\n    Total      float64\n    Status     string\n}\n\nfunc (q *OrderQuery) GetOrderSummary(ctx context.Context, orderID string) (*OrderView, error) {\n    return q.repo.GetOrderView(ctx, orderID)\n}
\n\n

Remarquez que OrderView contient déjà des données dénormalisées (nom de l'utilisateur, nom du produit) provenant de différentes sources. Cela accélère la lecture des dizaines de fois.

\n\n

Event Sourcing : enregistrer non pas l'état, mais les événements

\n

Event Sourcing change l'approche du stockage des données : au lieu de stocker l'état actuel d'un objet, nous stockons la séquence d'événements qui ont conduit à cet état. Cela offre un super-pouvoir : un historique complet des modifications et la possibilité de « rembobiner » l'état.

\n\n

Implémentation d'Event Sourcing en Go

\n

Commençons par les types d'événements de base :

\n\n
type Event struct {\n    ID        string\n    AggregateID string\n    Type      string\n    Data      []byte\n    Timestamp time.Time\n    Version   int\n}\n\ntype EventStore interface {\n    Save(ctx context.Context, events []Event) error\n    Load(ctx context.Context, aggregateID string) ([]Event, error)\n}
\n\n

Implémentons maintenant l'agrégat — l'objet qui restaure son état à partir des événements :

\n\n
type OrderAggregate struct {\n    ID     string\n    Status string\n    Items  []OrderItem\n    Version int\n}\n\nfunc (a *OrderAggregate) Apply(event Event) {\n    switch event.Type {\n    case "OrderCreated":\n        var data OrderCreatedData\n        json.Unmarshal(event.Data, &data)\n        a.ID = data.OrderID\n        a.Status = "created"\n    case "OrderConfirmed":\n        a.Status = "confirmed"\n    case "OrderShipped":\n        a.Stat                

Blogs

Book Recommendations