Conception pilotée par domaine (DDD) dans les microservices

Conception pilotée par domaine (DDD) dans les microservices

Lorsque les organisations passent d’une architecture monolithique aux microservices, elles sont confrontées à une question critique aux enjeux élevés : Comment tracer les limites de nos services ?

En théorie, les microservices devraient être des unités lâches et découplées qui peuvent être développées, déployées et mises à l’échelle indépendamment. Dans la pratique, cependant, de nombreuses équipes finissent par créer un monolithe distribué : un système dans lequel les services sont si étroitement couplés qu’un seul changement commercial nécessite de modifier et de déployer plusieurs services simultanément, ce qui aggrave la latence du réseau et les blocages de déploiement.

Pour éviter cet écueil, les architectes logiciels se tournent vers la Domain-Driven Design (DDD). Introduite pour la première fois par Eric Evans en 2003, DDD est une méthodologie de développement logiciel qui aligne les structures de code avec les domaines métier complexes qu’elles représentent.

Dans cet article, nous explorerons comment DDD fournit les plans stratégiques et tactiques pour concevoir des microservices propres, découplés et hautement maintenables.


1. Le plan stratégique : tracer les limites des services

DDD est divisé en deux phases principales : Conception stratégique et Conception tactique. La conception stratégique consiste à modéliser les contextes commerciaux et à définir les limites des services. Il fournit la vue « macro » de l’architecture.

Mappage des contextes délimités DDD au diagramme d'architecture de microservices

A. Langue omniprésente

Au sein d’une entreprise, différents départements utilisent les mêmes mots pour désigner des choses complètement différentes. Par exemple :

  • Pour l’équipe commerciale, un « utilisateur » est un prospect ou un client potentiel.
  • Pour l’équipe de sécurité, un « utilisateur » est un ensemble d’identifiants de connexion.
  • Pour l’équipe d’expédition, un « utilisateur » est une adresse physique de destinataire.

Essayer de créer un modèle de base de données unique et unifié pour un « utilisateur » qui satisfasse tout le monde aboutit à une base de code massive et enchevêtrée. DDD résout ce problème en établissant un langage omniprésent : un vocabulaire partagé défini et utilisé à la fois par les experts du domaine métier et les développeurs dans un cadre spécifique.

B. Contextes délimités

Un Contexte limité est la limite explicite à l’intérieur de laquelle un modèle de domaine s’applique. À l’intérieur de la frontière, tous les termes de la langue omniprésente ont une signification unique et sans ambiguïté.

Au lieu d’un seul modèle « Utilisateur », nous définissons des modèles distincts dans leurs contextes délimités respectifs :

  • Dans le Contexte de commande, nous avons un modèle Order contenant les coordonnées du client.
  • Dans le Contexte d’identité, nous avons un modèle Credentials.
  • Dans le Contexte d’expédition, nous avons un modèle DeliveryAddress.

La règle d’or des microservices : Un contexte délimité correspond directement à un microservice.

En divisant le système en contextes délimités, nous garantissons que les microservices sont faiblement couplés et représentent des capacités commerciales distinctes.

C. Cartographie du contexte : comment les services communiquent

Les contextes délimités n’existent pas de manière isolée ; ils doivent interagir. Une Context Map définit les relations et les mécanismes de traduction entre les contextes. Les modèles clés incluent :

  • Noyau partagé : deux contextes partagent un petit sous-ensemble du modèle de domaine et de la base de données (généralement déconseillé dans les microservices en raison du couplage).
  • Client-Fournisseur : Un contexte (fournisseur) doit fournir des données à un autre (client). Le fournisseur doit coordonner les calendriers de livraison avec le client.
  • Couche anti-corruption (ACL) : une couche de traduction qui traduit les données entrantes d’un système externe dans le modèle de domaine interne du client, empêchant ainsi les modèles externes de polluer l’architecture interne.

2. Le plan tactique : structurer la base de code du microservice

Une fois les limites du service définies à l’aide d’une conception stratégique, Tactical Design fournit un ensemble de modèles de conception pour structurer le code au sein d’un seul microservice.

A. Entités et objets de valeur

Au sein d’un microservice, les modèles sont divisés en deux catégories :

  1. Entités : objets qui ont une identité unique qui persiste dans le temps, même si leurs attributs changent. Les exemples incluent Order ou Product.
  2. Objets de valeur : objets qui n’ont pas d’identité unique et sont entièrement définis par leurs attributs. Ils sont immuables. Les exemples incluent ShippingAddress ou ProductDimensions. Si deux objets de valeur ont les mêmes attributs, ils sont considérés comme égaux.

B. Agrégats et racines d’agrégats

Un agrégat est un cluster d’entités et d’objets de valeur associés qui sont traités comme une unité unique pour les modifications de données. Chaque agrégat possède une racine d’agrégat, qui est le seul point d’entrée par lequel les objets externes peuvent interagir avec l’agrégat. La racine garantit que tous les invariants métier (règles) sont appliqués.

Par exemple, dans le schéma ci-dessus :

  • Dans le Service de commande, le Order est la racine agrégée. Il contient OrderItem (Entité) et ShippingAddress (Value Object). Les services externes ne peuvent pas modifier directement OrderItem ; ils doivent appeler une méthode sur la racine Order (par exemple, order.AddItem()), qui valide que la commande n’est pas déjà expédiée.

C. Événements de domaine

Un événement de domaine est un événement qui s’est produit dans le domaine et qui intéresse les experts métier. Il est utilisé pour communiquer les modifications dans les contextes délimités de manière asynchrone. Lorsqu’un agrégat exécute une commande, il publie un événement de domaine (par exemple, OrderCreated). D’autres services écoutent cet événement et mettent à jour leur état en conséquence, garantissant ainsi une cohérence éventuelle sans dépendances API synchrones.


3. Un exemple concret : service de commande vs service d’inventaire

Examinons une implémentation simplifiée du DDD tactique dans Go pour le Order Service, montrant comment la racine agrégée coordonne les règles métier.

package domain

import (
	"errors"
	"time"
)

// Value Object (Immutable, identity-less)
type ShippingAddress struct {
	Street  string
	City    string
	ZipCode string
}

// Entity (Has identity, mutable)
type OrderItem struct {
	ProductID string
	Quantity  int
	Price     float64
}

// Aggregate Root (Enforces transactional boundaries)
type Order struct {
	ID        string
	Items     []OrderItem
	Address   ShippingAddress
	Status    string
	CreatedAt time.Time
}

// NewOrder creates a new Order Aggregate Root
func NewOrder(id string, address ShippingAddress) *Order {
	return &Order{
		ID:        id,
		Items:     []OrderItem{},
		Address:   address,
		Status:    "PENDING",
		CreatedAt: time.Now(),
	}
}

// AddItem enforces business rules before mutating state
func (o *Order) AddItem(productID string, qty int, price float64) error {
	if o.Status != "PENDING" {
		return errors.New("cannot add items to a finalized or cancelled order")
	}
	if qty <= 0 {
		return errors.New("quantity must be greater than zero")
	}
	
	o.Items = append(o.Items, OrderItem{
		ProductID: productID,
		Quantity:  qty,
		Price:     price,
	})
	return nil
}

Lorsque la commande est enregistrée avec succès, le service publie un événement sur un courtier de messages :

{
  "event_id": "evt_98231",
  "event_type": "OrderCreated",
  "timestamp": "2026-06-26T00:15:00Z",
  "payload": {
    "order_id": "ord_5521",
    "items": [
      { "product_id": "prod_88", "quantity": 2 }
    ]
  }
}

Le Inventory Service écoute cet événement, met à jour son entité StockLevel locale et termine le flux de manière asynchrone.


4. DDD stratégique ou tactique dans les microservices

Phases/Aspects DDD stratégique DDD tactique
Portée Architecture système globale (macro) À l’intérieur d’une base de code de service unique (micro)
Objectif principal Tracez des limites de services claires et découplées Modélisez une logique métier riche et appliquez des invariants
Concepts clés Contextes délimités, langage omniprésent, carte contextuelle Entités, objets de valeur, agrégats, événements de domaine
Public Architectes, chefs de produits, développeurs Développeurs de logiciels, réviseurs de code
Impact sur les microservices Détermine le nombre et l’étendue des services Détermine les transactions de base de données et la structure des répertoires

Conclusion : commencez par le DDD stratégique

L’application de la conception pilotée par domaine aux microservices est un moyen puissant de garantir que votre architecture correspond à la structure de votre entreprise. Cependant, les équipes commettent souvent l’erreur de trop se concentrer sur des schémas tactiques (comme l’écriture d’interfaces de référentiel et d’objets de valeur) tout en ignorant la conception stratégique.

Si vous ne définissez pas correctement les limites, aucun code tactique propre n’empêchera votre système de se transformer en un monolithe distribué. Commencez toujours par une cartographie stratégique : définissez votre langage omniprésent, regroupez la logique dans des contextes délimités, cartographiez les flux de communication et laissez les limites de vos microservices émerger naturellement de ces définitions.


Découvrez davantage d’architecture logicielle, de modèles de conception et d’informations techniques sur le blog Ghaznix →