archi3

Clean Architecture

La règle de dépendance : le cœur ne connaît jamais l'extérieur

Formalisée par Robert C. Martin ("Uncle Bob") en 2012, la Clean Architecture pose une seule règle non négociable : les dépendances du code source ne peuvent pointer que vers l'intérieur. Tout le reste - les quatre cercles, les noms des couches - n'est qu'une manière concrète d'appliquer cette règle.

Lecture : 10 minutesVoir le code sur GitHub

Frameworks & Drivers

web, DB, UI

Adaptateurs d'interface

contrôleurs, présentateurs

Cas d'usage

orchestration métier

Entités

règles métier

Les dépendances du code source pointent uniquement vers l'intérieur.

Origine et objectif

En 2012, Robert Martin publie un article de blog intitulé "The Clean Architecture", dans lequel il remarque que plusieurs architectures apparues indépendamment - l'architecture hexagonale de Cockburn, l'architecture Onion de Jeffrey Palermo, le DCI - convergent toutes vers les mêmes objectifs.

Ces objectifs : un système testable sans interface graphique, sans base de données, sans serveur web ; une base de données et un framework qui restent des détails, remplaçables sans toucher aux règles métier ; des règles métier qui ignorent totalement le monde extérieur.

Plutôt que d'inventer une nouvelle idée, Martin propose une représentation unifiée - les quatre cercles concentriques - et un nom pour la règle qui les relie tous : la règle de dépendance.

La règle de dépendance

La règle de dépendance dit ceci : le code source d'un cercle ne peut faire référence qu'à des éléments de cercles plus internes ou du même cercle. Rien dans un cercle interne ne doit connaître le nom d'une classe, d'une fonction ou d'une variable définie dans un cercle plus externe.

Cela inclut les formats de données. Si le cercle interne a besoin d'une donnée produite par un cercle externe, on ne passe pas directement une structure du framework : on définit une interface simple, propriété du cercle interne, et c'est le cercle externe qui l'implémente. C'est le principe d'inversion de dépendance appliqué à l'architecture entière.

Les quatre cercles

Le schéma original distingue quatre cercles, du plus interne au plus externe :

  • Entités : les objets et règles métier les plus générales de l'entreprise, celles qui survivraient même en changeant complètement d'application. Elles ne dépendent de rien.
  • Cas d'usage (Use Cases) : l'orchestration spécifique à l'application - "passer une commande", "annuler un abonnement". Ils utilisent les entités et définissent, sous forme d'interfaces, ce dont ils ont besoin de l'extérieur.
  • Adaptateurs d'interface (Interface Adapters) : contrôleurs, présentateurs, mappers. Ils traduisent entre le format pratique pour les cas d'usage et le format pratique pour la technologie externe (HTTP, SQL, JSON).
  • Frameworks & Drivers : la couche la plus externe - base de données, framework web, UI. Le code y est réduit au minimum, essentiellement de la configuration et du câblage.

Et par rapport à l'architecture hexagonale ?

Les deux approches défendent exactement la même idée avec un vocabulaire différent. Le noyau hexagonal (domaine) correspond aux deux cercles internes de Clean Architecture (Entités + Cas d'usage). Les ports de l'hexagonale correspondent aux interfaces que les cas d'usage définissent pour parler à l'extérieur.

La vraie valeur ajoutée de Clean Architecture, c'est de nommer explicitement la couche "Adaptateurs d'interface" comme une chose à part entière - alors que l'hexagonale la laisse souvent fusionnée avec les adaptateurs. Cette couche intermédiaire (contrôleurs, présentateurs, mappers) a pour seul rôle de traduire les formats, sans porter de règle métier.

En pratique, un même projet peut se réclamer des deux : "architecture hexagonale organisée selon la règle de dépendance de Clean Architecture" décrit très bien la majorité des codebases sérieuses qui suivent ces principes.

Exemple concret : le même cas d'usage, en couches explicites

Reprenons le cas d'usage "passer une commande". En Clean Architecture, on distingue explicitement l'entité (qui porte les règles de validation intrinsèques) du cas d'usage (l'interactor, qui orchestre) et du port qu'il définit pour la persistance.

entities/Order.kt
// Entité : règle métier intrinsèque, zéro dépendance externe
class Order private constructor(
    val id: OrderId,
    private val items: List<OrderLine>,
    private val status: OrderStatus,
) {
    companion object {
        fun create(id: OrderId, items: List<OrderLine>): Order {
            if (items.isEmpty()) throw EmptyOrderError()
            return Order(id, items, OrderStatus.Pending)
        }
    }

    fun totalAmount(): Money =
        items.fold(Money.zero()) { sum, line -> sum.add(line.subtotal()) }
}
usecases/PlaceOrderInteractor.kt
// Cas d'usage : orchestration, définit ce dont il a besoin via une interface
interface OrderGateway {
    suspend fun save(order: Order)
}

class PlaceOrderInteractor(private val orders: OrderGateway) : PlaceOrder {

    override suspend fun execute(command: PlaceOrderCommand): OrderId {
        val order = Order.create(OrderId.generate(), command.items)
        orders.save(order)
        return order.id
    }
}

Pièges fréquents

Trois erreurs reviennent souvent lors de l'adoption de Clean Architecture :

  • Des entités anémiques : de simples structures avec getters/setters, sans aucune règle métier, alors que l'entité devrait porter les invariants (comme le refus d'une commande vide dans l'exemple ci-dessus).
  • Des annotations de framework sur les entités (JPA, Mongoose, décorateurs d'un ORM) : cela fait dépendre le cercle le plus interne d'une bibliothèque externe, en violation directe de la règle de dépendance.
  • Empiler les quatre cercles littéralement, avec quatre dossiers et autant de fichiers, sur une application qui a en réalité deux ou trois règles métier simples : la règle de dépendance compte, pas le nombre de dossiers.