DDD, Vertical Slice, CQRS, Hexagonale : les devs confondent tout — mettons de l’ordre

Le grand flou architecturalDans 90 % des projets, on entend :> « On fait du DDD. »
« Non, on fait du CQRS. »
« Non, c’est une architecture hexagonale ! »… alors qu’en réalité, le projet est juste un CRUD bien rangé en couches.
Ces concepts sont complémentaires, mais pas interchangeables.
Ils agissent à différents niveaux de conception : structure, séparation de responsabilités, organisation du code et modélisation du métier.## 4 familles de patterns| Concept | Type | Objectif principal | | --- | --- | --- | | DDD (Domain-Driven Design) | Méthodologie / modélisation | Refléter la réalité métier dans le code | | Vertical Slice Architecture | Organisation du code | Structurer par fonctionnalité (use case) plutôt que par couche | | CQRS | Pattern d’exécution | Séparer lecture et écriture | | Hexagonale (Ports & Adapters) | Style d’architecture | Isoler le domaine des détails techniques |## L’architecture en couches (Layered CRUD)Le but est de séparer les responsabilité techniques.
Comme structurer son projet ainsi :``` /Controllers /Services /Repositories /Models
Le **Layered Crud**, pertinent pour les projets **data-centric**.**Diagramme — Architecture en couches**## Vertical Slice Architecture — penser “use case”Type : **Feature-centric**
*Organiser le code **par fonctionnalité**, pas par couche technique.
*Pertinent pour des projets modulaires ou évolutifs.```
/Features
/Users
/Create
CreateUserCommand.cs
CreateUserHandler.cs
CreateUserEndpoint.cs
/GetById
GetUserQuery.cs
GetUserHandler.cs
GetUserEndpoint.cs
```**Exemple :**```
public record CreateUserCommand(string Email, string Password) : IRequest<Guid>;
public class CreateUserHandler : IRequestHandler<CreateUserCommand, Guid>
{
private readonly IUserRepository _repo;
public CreateUserHandler(IUserRepository repo) => _repo = repo;
public async Task<Guid> Handle(CreateUserCommand cmd, CancellationToken ct)
{
var user = new User(cmd.Email, cmd.Password);
await _repo.AddAsync(user, ct);
return user.Id;
}
}
```**Forces :**- Chaque slice est indépendante et testable
- Structure claire : un use case = un dossier
- Parfait avec **MediatR** et **Minimal APIs****Limites :**- Nécessite rigueur pour garder les slices cohérentes
- Risque d’éparpillement si le projet grossit sans guidelines**Diagramme - Organisation Vertical Slice***voir l'article => [https://xavierraboteur.fr/2025/10/14/vertical-slice-modular-monolith-le-sweet-spot/](/articles/vertical-slice-modular-monolith-le-sweet-spot)*## CQRS — Command Query Responsibility SegregationType : **Behavior-centric**
*Idéal pour les systèmes à fort volume ou fort métier.
*CQRS (Command Query Responsibility Segregation) ne parle **ni de couches**, ni de domaine.
Il dit simplement : **séparons l’écriture et la lecture**.- **Command** → modifie l’état (pas de retour riche)
- **Query** → lit l’état (sans effet de bord)```
/Features
/Orders
/CreateOrder (Command)
/GetOrderById (Query)
```**Exemple :**```
// Command
public record CreateOrderCommand(Guid CustomerId, decimal Amount) : IRequest<Guid>;
// Query
public record GetOrderByIdQuery(Guid Id) : IRequest<OrderDto>;
// Command Handler
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, Guid>
{
private readonly IOrderRepository _repo;
public async Task<Guid> Handle(CreateOrderCommand cmd, CancellationToken ct)
{
var order = new Order(cmd.CustomerId, cmd.Amount);
await _repo.AddAsync(order);
return order.Id;
}
}
// Query Handler
public class GetOrderByIdHandler : IRequestHandler<GetOrderByIdQuery, OrderDto>
{
private readonly AppDbContext _db;
public Task<OrderDto> Handle(GetOrderByIdQuery q, CancellationToken ct)
=> _db.Orders
.Where(o => o.Id == q.Id)
.Select(o => new OrderDto(o.Id, o.Amount))
.FirstAsync(ct);
}
```**Schéma de flux CQRS****Avantages :**- Clarifie les intentions : lire ≠ modifier
- Facilite l’optimisation des modèles de lecture
- Compatible avec **Event Sourcing****Limites :**- Peut alourdir les petits projets
- Demande une vraie discipline d’équipe## DDD — le métier au centreType : **Domain-centric**
*Parfait pour les domaines riches, évolutifs, critiques.
*Le **Domain-Driven Design** n’est pas une architecture,
c’est une **philosophie de conception**.On conçoit le logiciel **autour du langage métier** et de ses **invariants**.```
/Domain
/Aggregates
/Order.cs
/Customer.cs
/ValueObjects
/DomainEvents
/Application
/Features (Command/Query)
```**Exemple :**```
public class Order : AggregateRoot
{
private readonly List<OrderLine> _lines = new();
public Order(Guid customerId)
{
CustomerId = customerId;
Status = OrderStatus.Created;
}
public void AddLine(Product product, int quantity)
{
if (Status != OrderStatus.Created)
throw new DomainException("Cannot add line to finalized order");
_lines.Add(new OrderLine(product.Id, quantity));
AddDomainEvent(new OrderLineAddedEvent(Id, product.Id));
}
}
```**Model DDD (simplifié)****Avantages :**- Le code parle le langage du métier
- Règles, invariants et comportements explicites
- Se combine parfaitement avec CQRS et Hexagonale**Limites :**- Complexe à mettre en place pour un simple CRUD
- Nécessite une forte collaboration avec le métier## Architecture Hexagonale — Ports & AdaptersType : **Isolation-centric**
*Idéal pour les systèmes durables, modulaires, testables.*
Aussi appelée **Ports and Adapters** :
le **cœur métier** est isolé de tout ce qui est technique (API, DB, UI…).```
/Domain
/Application
/Infrastructure
/Adapters
/Database
/Messaging
/Api
```**Exemple :**```
// Port
public interface IOrderRepository
{
Task AddAsync(Order order);
}
// Adapter (EF Core)
public class EfOrderRepository : IOrderRepository
{
private readonly AppDbContext _db;
public Task AddAsync(Order order) => _db.Orders.AddAsync(order);
}
```**Avantages :**- Domaine indépendant des frameworks
- Testabilité maximale
- Évolutivité naturelle : remplacer EF, Kafka, etc.**Schéma Hexagonal (Ports & Adapters)**## Comment tout ça s’articuleCes patterns **ne s’opposent pas**, ils se **superposent** :| Niveau | Pattern | Objectif |
| --- | --- | --- |
| **Modélisation** | DDD | Refléter le métier |
| **Organisation du code** | Vertical Slice | Structurer par use case |
| **Flux de données** | CQRS | Séparer Command/Query |
| **Structure logicielle** | Hexagonale | Isoler le domaine des détails techniques |### Trouver le bon équilibre architecturalChaque architecture répond à une intention, un contexte, un degré de maturité.
Choisissez la combinaison la plus juste selon la nature de votre projet :- **Pour un CRUD simple**, une approche légère en **Vertical Slice** associée à un **CQRS minimal** suffit amplement : clarté, rapidité, efficacité.
- **Pour un domaine métier complexe**, privilégiez une base solide en **DDD**, enrichie du couple **CQRS + Hexagonale** pour préserver la cohérence et la testabilité du cœur métier.
- **Pour un produit SaaS conçu pour durer**, misez sur la **durabilité** : **DDD** pour le sens, **Hexagonale** pour l’isolation, **Vertical Slice** pour la modularité et l’évolution continue.
- **Pour un MVP ou prototype rapide**, commencez simplement par une architecture **Vertical Slice**. Vous pourrez ensuite la faire évoluer naturellement vers un modèle **DDD** à mesure que la complexité fonctionnelle se révélera.> En somme, chaque projet mérite son dosage : l’architecture n’est pas un dogme, mais une stratégie au service de la clarté et de la longévité du code.## ConclusionLes développeurs ne devraient plus dire :> “On fait du DDD.”
> ou
> “Notre projet est CQRS.”Mais plutôt :> “On a un **domaine riche** (DDD), organisé **par slice** (Vertical Slice),
> avec une **séparation lecture/écriture** (CQRS)
> et une **isolation du cœur métier** (Hexagonale).”Ces approches ne s’excluent pas.
Elles forment **un écosystème cohérent** pour construire des applications **vivantes, testables et durables**.