Clean Architecture : les 3 règles qui suffisent
Clean Architecture : les 3 règles qui suffisent
J'accompagnais une équipe engineering chez Canal+ (20 développeurs). Une API de 45 endpoints, 4 ans de code accumulé, une couverture de tests à 28%. Les nouveaux développeurs atteignaient leur autonomie en 7 semaines. Pas parce qu'ils étaient mauvais, parce que le code était impossible à naviguer.
Ils m'ont demandé si la Clean Architecture pouvait les aider. Ma réponse : oui, mais pas telle qu'on l'applique généralement.
J'ai introduit 3 règles, pas les 8 couches du diagramme en cercles de Robert Martin. En 4 mois : couverture de tests passée de 28% à 67%, lead time baissé de 35%, onboarding raccourci de 7 à 3 semaines.
La Clean Architecture n'est pas une religion. C'est un outil. Et comme tout outil, c'est la maîtrise qui compte, pas la conformité.
Pourquoi la Clean Architecture divise les équipes
L'architecture "full Clean" avec des entités, des use cases, des interfaces presenters, des controllers, des gateways se justifie dans des contextes spécifiques : applications complexes avec des règles métier riches, systèmes qui doivent changer de framework ou de base de données, projets long-terme avec de nombreux développeurs.
Dans la plupart des contextes (une API REST de taille moyenne, une application en phase de croissance), la mise en place complète génère plus de complexité qu'elle n'en résout. Les symptômes sont toujours les mêmes : des dizaines de fichiers pour chaque feature, des indirections qui rendent le debug cauchemardesque, des juniors qui passent 3 jours à comprendre où écrire un bout de logique.
Le problème n'est pas la Clean Architecture. C'est l'application dogmatique de sa forme sans en comprendre l'essence.
Robert C. Martin (Uncle Bob) a formalisé ces principes dans son livre éponyme de 2017. Ce qu'il dit, et que beaucoup oublient : les couches ne sont pas une prescription rigide. Ce sont des guides. L'essence, elle, tient en 3 règles. Et quand une équipe adopte cette architecture, documenter les décisions structurantes dans des Architecture Decision Records évite de refaire les mêmes débats à chaque nouveau développeur.
Règle 1 : La règle de dépendance : les flèches pointent vers l'intérieur
C'est la règle fondamentale. Dans un système bien architecturé, les modules de haut niveau (métier, règles business) ne doivent jamais dépendre des modules de bas niveau (frameworks, base de données, HTTP).
La dépendance va dans une seule direction : de l'extérieur vers l'intérieur. Les couches externes dépendent des couches internes. Jamais l'inverse. C'est exactement ce que formule le Dependency Inversion Principle, et l'appliquer concrètement est le premier pas vers une codebase testable et maintenable.
Ce que cela signifie concrètement :
- Votre logique métier ne doit pas importer Spring, Express, Django, ou aucun framework
- Votre logique métier ne doit pas importer de driver de base de données ou d'ORM
- Votre logique métier ne doit pas connaître le format HTTP (statut codes, headers)
// Mauvais : la logique métier dépend de Spring
@Service
public class OrderService {
@Autowired
private OrderRepository repository; // dépendance sur Spring Data
public void processOrder(Long orderId) {
Order order = repository.findById(orderId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); // dépendance sur Spring HTTP
// logique métier...
}
}
// Mieux : la logique métier ne connaît pas Spring
public class ProcessOrderUseCase {
private final OrderRepository repository; // interface, pas Spring Data
public void execute(OrderId orderId) {
Order order = repository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId)); // exception métier
// logique métier pure...
}
}
Pourquoi c'est important : si votre logique métier est indépendante des frameworks, vous pouvez la tester sans démarrer de serveur HTTP et sans base de données. Vos tests métier s'exécutent en millisecondes, ce qui résout précisément le problème des tests d'intégration sur du code legacy où chaque test démarre une vraie infrastructure. Et si vous changez de framework un jour, la logique métier n'est pas touchée.
Règle 2 : Les entités métier ne connaissent pas l'infrastructure
Vos objets métier (Order, User, Product, Contract) doivent pouvoir exister et être testés sans base de données, sans framework de persistance, sans format de sérialisation.
Ce que cela signifie concrètement :
- Pas d'annotations JPA (
@Entity,@Column) sur vos classes métier - Pas de décorateurs de sérialisation (
@JsonProperty) sur vos domaines objects - Pas d'héritage de classes framework dans vos entités métier
// Entité métier polluée par l'infrastructure
@Entity()
@Table({ name: 'orders' })
export class Order {
@PrimaryGeneratedColumn()
@ApiProperty() // annotation Swagger
id: number;
@Column()
@IsNotEmpty() // annotation validation HTTP
customerId: number;
}
// Entité métier pure
export class Order {
constructor(
public readonly id: OrderId,
public readonly customerId: CustomerId,
private status: OrderStatus
) {}
confirm(): void {
if (this.status !== OrderStatus.PENDING) {
throw new OrderAlreadyConfirmedException(this.id);
}
this.status = OrderStatus.CONFIRMED;
}
}
// La persistance est gérée par une entité séparée dans la couche infrastructure
L'erreur courante : mettre les annotations de persistance directement sur les objets métier par pragmatisme. Ça fonctionne pour les petites applications. Sur une application qui grandit, ça crée un couplage fort entre la représentation métier et le schéma de base de données, deux choses qui évoluent à des rythmes différents.
Votre architecture produit de la complexité sans apporter les bénéfices attendus ?
Un audit architectural de votre codebase identifie les couplages problématiques, les opportunités de simplification, et la roadmap pour une architecture qui supporte votre croissance sans vous ralentir. En 30 minutes, on peut établir un premier diagnostic et définir les priorités.
Règle 3 : Les use cases orchestrent sans dépendre du delivery mechanism
Un use case implémente un cas d'utilisation métier. Il orchestre des entités, appelle des repositories, et retourne un résultat.
Ce qu'il ne doit pas faire : connaître comment son résultat va être utilisé : HTTP response, message queue, CLI output, test assertion. Le use case retourne un objet métier. La couche externe décide de comment le sérialiser.
Ce que cela signifie concrètement :
- Un use case ne retourne pas de
ResponseEntity<>ou d'objet HTTP - Un use case ne lève pas d'exceptions HTTP (
HttpStatus.NOT_FOUND) - Un use case peut être appelé depuis un controller HTTP, un consumer de message queue, ou un test sans modification
# Use case indépendant du delivery mechanism
class CreateUserUseCase:
def __init__(self, user_repository: UserRepository, email_service: EmailService):
self.user_repository = user_repository
self.email_service = email_service
def execute(self, command: CreateUserCommand) -> User:
if self.user_repository.exists_by_email(command.email):
raise EmailAlreadyExistsError(command.email) # exception métier
user = User.create(command.email, command.name)
self.user_repository.save(user)
self.email_service.send_welcome(user)
return user # retourne un objet métier, pas un objet HTTP
# Le controller HTTP gère la sérialisation
class UserController:
def create_user(self, request: CreateUserRequest) -> Response:
try:
command = CreateUserCommand(request.email, request.name)
user = self.create_user_use_case.execute(command)
return Response(UserDTO.from_domain(user), status=201)
except EmailAlreadyExistsError:
return Response({"error": "Email already exists"}, status=409)
Les anti-patterns courants à éviter
L'Anemic Domain Model : les entités ne contiennent que des getters/setters, et toute la logique est dans les services. Le domaine est vide de sens. La logique métier se retrouve dispersée dans tous les services et est difficile à tester isolément.
Le God Service : un service qui fait tout : valide, orchestre, persiste, formate. Difficile à tester, impossible à réutiliser, cauchemar à maintenir.
Le leak d'infrastructure : des dépendances sur des frameworks qui se propagent jusqu'aux entités métier. Le signe infaillible que la règle 2 est violée.
Quand appliquer les 3 règles, quand ne pas les appliquer
Appliquer dès que : la logique métier est non-triviale, l'application doit durer plus de 2 ans, ou plusieurs développeurs travaillent sur le même codebase.
Ne pas appliquer dans sa totalité : pour des scripts, des prototypes, des outils internes à courte durée de vie. La Clean Architecture a un coût d'entrée qui doit être justifié par la longévité et la complexité du projet.
FAQ sur la Clean Architecture
1. La Clean Architecture est-elle compatible avec les frameworks comme Spring ou NestJS ?
Oui, mais avec discipline. Spring et NestJS sont excellents comme couches d'infrastructure (injection de dépendances, routing HTTP). La règle est de ne pas laisser leurs annotations et abstractions contaminer les couches métier. Utiliser la DI du framework pour injecter les dépendances dans les use cases, sans que les use cases aient connaissance du framework lui-même.
2. Faut-il refactoriser tout un codebase existant pour appliquer ces 3 règles ?
Non. Appliquer les 3 règles progressivement, au fil des nouvelles features et des refactorings planifiés. Commencer par les modules les plus actifs, ceux sur lesquels l'équipe travaille le plus souvent. Sur les modules legacy stables, le coût du refactoring n'est pas justifié si le gain opérationnel est faible. C'est la stratégie que j'applique systématiquement dans mes missions.
3. La Clean Architecture ralentit-elle le développement au départ ?
Légèrement, sur les 2 à 4 premières semaines. Le coût est l'apprentissage de la structuration correcte du code. Après cette période, le développement accélère parce que les tests sont rapides, les changements sont localisés, et les nouveaux développeurs comprennent la structure rapidement. Sur l'équipe dont je parlais en ouverture, le ralentissement initial a été compensé dès le premier mois.
4. Clean Architecture, Hexagonal Architecture, Onion Architecture : quelle différence ?
Ce sont des variantes du même principe : séparer le domaine de l'infrastructure et diriger les dépendances vers le centre. Les différences sont principalement de vocabulaire et d'emphase. L'Hexagonal (ports et adapters) est souvent plus simple à expliquer. La Clean Architecture est plus prescriptive sur les couches. Choisissez le vocabulaire qui résonne avec votre équipe. Le principe est le même.
5. Comment justifier le temps d'investissement en Clean Architecture auprès du management ?
Trois arguments concrets que j'utilise : (1) Les tests métier s'exécutent en secondes et non en minutes, réduisant le feedback loop et le temps de CI. (2) Les nouveaux développeurs atteignent l'autonomie 2 à 4 semaines plus tôt, ce qui représente une économie de 40 à 80 heures d'accompagnement par recrutement. (3) Les changements de framework ou de base de données, qui arrivent tous les 3 à 5 ans, ne nécessitent pas de réécriture de la logique métier. Pour un CTO qui prend ses fonctions, investir dans ces fondations architecturales fait partie des décisions structurantes à prendre dans les 90 premiers jours, avant que la dette s'accumule et rende le changement trop coûteux.
Ressource gratuite : Engineering Maturity Self-Assessment
L'Engineering Maturity Self-Assessment couvre le domaine Architecture & Craft : évaluez votre niveau sur la séparation des couches, le couplage, et la testabilité de votre codebase. Score et recommandations en 10 minutes.