Communitygithub.com

Domain-Driven Design — Patrons et Pratiques

Compétences d'ingénierie prêtes à l'emploi et bonnes pratiques pour votre assistant IA, basées sur le Domain-Driven Design.

Qu'est-ce que Domain-Driven Design — Patrons et Pratiques ?

Cette compétence fournit un guide complet du Domain-Driven Design (DDD) pour les assistants IA et les développeurs. Elle couvre à la fois le design stratégique (Contextes Bornés, Cartes de Contexte) et le design tactique (Entités, Objets-Valeurs, Agrégats, Événements de Domaine, Services et Dépôts). L'utilisateur y trouvera des règles claires pour savoir quand adopter le DDD (logique métier complexe, avantage concurrentiel) et quand l'éviter (CRUD simple, hackathons). La compétence détaille les principes d'architecture propre (Clean Architecture) avec des dépendances strictement orientées vers l'intérieur, et des exemples concrets en TypeScript illustrant la différence entre un modèle anémique (à éviter) et un modèle riche avec invariants. Des anti-patrons typiques sont listés et expliqués (agrégats trop gros, transactions cross-agrégats, fuite d'infrastructure dans le domaine). L'assistant peut ainsi guider la conception de systèmes robustes et maintenables en respectant les règles du DDD.

Compatible avec✓Claude Code~Codex CLI~Cursor
npx skills add kok-o/koko-contextos-agents

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Que fait Domain-Driven Design — Patrons et Pratiques ?

Quand utiliser le DDD

À utiliser lorsque :

  • La logique métier complexe dépasse le simple CRUD
  • Plusieurs experts du domaine utilisent des vocabulaires différents
  • Le modèle métier est l'avantage concurrentiel
  • Applications de niveau entreprise

Ne pas utiliser lorsque :

  • Applications CRUD simples
  • Hackathon / MVP (trop lourd)
  • Aucun expert du domaine disponible

Design Stratégique

Contextes Bornés

Le concept le plus important du DDD. Un Contexte Borné est une frontière à l'intérieur de laquelle un modèle particulier est défini et applicable.

Exemple — E-Commerce :

[Contexte Commande]   [Contexte Paiement]     [Contexte Expédition]
  - Commande             - Paiement               - Expédition
  - ArticleDeCommande    - Transaction             - NuméroDeSuivi
  - Client (ref)         - Remboursement           - Adresse
  - Adresse (valeur)     - Facture                 - Transporteur

Client signifie des choses différentes dans chaque contexte :

  • Contexte Commande : nom, email, préférence de livraison
  • Contexte Paiement : informations de facturation, moyens de paiement
  • Contexte Support : historique des tickets, score de satisfaction

Carte de Contexte

[Commande] ←→ [Paiement]     # Partenariat
[Commande] → [Expédition]    # Client-Fournisseur
[Commande] → [CRM Hérité]    # Couche Anti-Corruption

Design Tactique

Entités

Objets avec identité. Deux entités ayant les mêmes attributs mais des ID différents sont différentes.

class User {
  readonly id: UserId;
  name: string;
  email: Email;  // Objet-Valeur
}

Objets-Valeurs

Objets définis par leurs attributs, pas par leur identité. Immuables.

class Email {
  constructor(readonly value: string) {
    if (!isValidEmail(value)) throw new InvalidEmailError(value);
  }
  equals(other: Email): boolean {
    return this.value === other.value;
  }
}

Agrégats

Un regroupement d'entités et d'objets-valeurs avec une seule entité racine (Racine d'Agrégat). Tout accès passe par la racine.

class Order {  // Racine d'Agrégat
  private items: OrderItem[] = [];
  
  addItem(product: ProductRef, quantity: number): void {
    // Logique métier ICI, pas dans un service
    if (quantity <= 0) throw new InvalidQuantityError();
    this.items.push(new OrderItem(product, quantity));
  }
  
  get total(): Money {
    return this.items.reduce((sum, item) => sum.add(item.subtotal), Money.zero());
  }
}

Règles des Agrégats :

  1. Référencer les autres agrégats uniquement par leur ID
  2. Un agrégat par transaction
  3. Cohérence éventuelle entre les agrégats

Événements de Domaine

Quelque chose qui s'est produit dans le domaine et qui intéresse les experts du domaine.

class OrderPlaced implements DomainEvent {
  constructor(
    readonly orderId: OrderId,
    readonly customerId: CustomerId,
    readonly total: Money,
    readonly occurredAt: Date
  ) {}
}

Services de Domaine

Logique métier qui n'appartient pas naturellement à une entité ou à un objet-valeur.

class PricingService {
  calculatePrice(order: Order, customer: Customer, promotions: Promotion[]): Money {
    // Logique de tarification complexe impliquant plusieurs agrégats
  }
}

Dépôts

Abstraction sur l'accès aux données. Un dépôt par racine d'agrégat.

interface OrderRepository {
  findById(id: OrderId): Promise<Order | null>;
  save(order: Order): Promise<void>;
  delete(id: OrderId): Promise<void>;
}

Structure de Répertoire (DDD)

src/
├── modules/
│   └── orders/                    # Contexte Borné
│       ├── domain/
│       │   ├── entities/
│       │   │   └── order.ts       # Racine d'Agrégat
│       │   ├── value-objects/
│       │   │   └── money.ts
│       │   ├── events/
│       │   │   └── order-placed.ts
│       │   ├── services/
│       │   │   └── pricing.ts
│       │   └── repositories/
│       │       └── order.repository.ts  # Interface
│       ├── application/
│       │   ├── commands/
│       │   │   └── place-order.ts
│       │   ├── queries/
│       │   │   └── get-order.ts
│       │   └── handlers/
│       │       └── place-order.handler.ts
│       └── infrastructure/
│           ├── persistence/
│           │   └── order.repository.impl.ts  # Implémentation
│           └── api/
│               └── orders.controller.ts

La Règle de Dépendance de l'Architecture Propre

En DDD, les dépendances DOIVENT strictement pointer vers l'intérieur :

[ Frameworks & Drivers (Web, DB, UI) ]
      └──▶ [ Adaptateurs d'Interface (Contrôleurs, Passerelles) ]
            └──▶ [ Application (Cas d'Utilisation, Handlers CQRS) ]
                  └──▶ [ Domaine (Entités, Objets-Valeurs) ]
  • La couche Domaine n'a AUCUNE dépendance vers les ORM (Prisma, TypeORM), les frameworks HTTP (Express, NestJS) ou les SDK externes.
  • Les dépôts sont définis comme des interfaces dans la couche domaine/application et implémentés dans la couche infrastructure.

Événements de Domaine vs Événements d'Intégration

  1. Événements de Domaine : Représentent des changements d'état à l'intérieur d'un seul Contexte Borné.
    • Levés directement à l'intérieur de la Racine d'Agrégat (order.addItem(...) lève OrderItemAdded).
    • Distribués in-process avant le commit de la transaction.
  2. Événements d'Intégration : Publiés entre les frontières des Contextes Bornés pour communiquer avec d'autres services.
    • Distribués via le modèle Transactional Outbox vers les courtiers de messages.
    • Doivent utiliser des schémas rétrocompatibles avec versionnage.

Couche Anti-Corruption (ACL)

Lors de la consommation de données provenant d'un contexte borné externe ou d'une API tierce (ex. Stripe, Salesforce) :

  • N'importez JAMAIS les modèles de domaine externes directement dans votre domaine.
  • Créez un Traducteur / Adaptateur ACL dans la couche infrastructure pour convertir les DTO externes en vos propres Objets-Valeurs et Entités.

Anti-Patrons

  • [FAIL] Modèle de domaine anémique — entités avec seulement getters/setters, toute la logique dans les services
  • [FAIL] Agrégat trop gros — les agrégats doivent être petits, concentrés sur les invariants
  • [FAIL] Transactions cross-agrégats — utiliser la cohérence éventuelle
  • [FAIL] DDD partout — n'utiliser le DDD que là où la complexité le justifie
  • [FAIL] Entités ORM qui fuient dans le Domaine — les entités du domaine ne doivent pas dépendre de @Entity() ou de décorateurs ORM

Exemples de Code

Voir EXAMPLES.md pour des exemples de code détaillés.

Liste de Vérification

Ce qu'il faut vérifier pendant la phase de relecture avant de terminer la tâche.

Erreurs Courantes

Anti-patrons et choses à éviter explicitement. Voir TROUBLESHOOTING.md.

Notes d'Intégration

Comment cette compétence interagit avec d'autres compétences.


Exemples DDD — Anti-patrons vs Standard ContextOS

Exemple 1 : Entités du Domaine vs Modèles Anémiques

Anti-patron : Modèle de Domaine Anémique avec Setters qui Fuient

// MAUVAIS : Aucun invariant métier ; n'importe quel appelant peut corrompre l'état
class BankAccount {
  public balance: number = 0;
  public isFrozen: boolean = false;
}

// Logique qui fuit dans le contrôleur ou le service
account.balance -= 500; // Découvert non vérifié !

Bonne pratique : Standard ContextOS (Modèle de Domaine Riche avec Invariants Protégés)

// BON : Les invariants sont strictement appliqués à l'intérieur de la Racine d'Agrégat
class BankAccount {
  private _balance: number;
  private _isFrozen: boolean;

  constructor(id: string, initialDeposit: Money) {
    this._balance = initialDeposit.amount;
    this._isFrozen = false;
  }

  public withdraw(amount: Amount): void {
    if (this._isFrozen) {
      throw new AccountFrozenException('Impossible de retirer d\'un compte gelé');
    }
    if (this._balance < amount.amount) {
      throw new InsufficientFundsException('Fonds insuffisants pour le retrait');
    }
    this._balance -= amount.amount;
    this.addDomainEvent(new MoneyWithdrawnEvent(this.id, amount));
  }
}

Dépannage & Erreurs Courantes du DDD

1. Agrégats Dieu

  • Symptôme : La Racine d'Agrégat contient 20 entités enfants et son chargement nécessite des jointures sur des dizaines de tables.
  • Cause Racine : Traiter les tables d'un diagramme ERD comme des frontières d'agrégats plutôt que comme des unités de cohérence transactionnelle.
  • Correctif : Concevoir des agrégats de petite taille. Référencer les autres agrégats uniquement par leur ID, pas par référence d'objet.

2. Fuite de l'Infrastructure dans la Couche Domaine

  • Symptôme : Les entités du domaine importent Prisma, des décorateurs TypeORM, ou des objets Request Express.
  • Cause Racine : Inversion des frontières de l'Architecture Propre.
  • Correctif : La couche Domaine doit être du TypeScript pur avec zéro dépendance externe de framework.

3. Transaction couvrant plusieurs Agrégats

  • Symptôme : Conflits de verrouillage élevés et blocages sous des transactions concurrentes.
  • Cause Racine : Modification de plusieurs racines d'agrégats dans la même transaction de base de données.
  • Correctif : Règle empirique : exactement une Racine d'Agrégat modifiée par transaction. Utiliser les Événements de Domaine pour la cohérence éventuelle entre les autres agrégats.

Skills associés