Architecture logicielle : découper une application en couches
Quand les développeurs d'un projet partagent la même vision de sa structure, ils retrouvent plus vite où modifier le code, se marchent moins sur les pieds et se comprennent mieux. Le découpage en couches est la vision la plus répandue : vous l'avez probablement déjà vue en cours, et elle sera familière à la plupart des développeurs expérimentés. Ce n'est pas la seule manière de structurer un projet, mais c'est une base solide, et les approches plus récentes (voir plus bas) en sont des variantes.
Les cinq couches
Une application peut se décrire comme une pile de cinq couches. Chacune a une responsabilité précise et s'appuie sur celle qui est en dessous.
| Couche | Son rôle | Elle ne doit pas contenir |
|---|---|---|
| Frontière (présentation, IHM) | Afficher les données et recueillir les saisies : pages HTML, écrans, réponses JSON d'une API. | de règles de gestion, de requêtes SQL |
| Contrôleur | Dérouler un cas d'utilisation (« réserver un livre ») : il appelle les objets métier, fait appel à la persistance et fournit à la frontière ce qu'elle doit afficher. | de règles de gestion détaillées, de SQL, de HTML |
| Métier (domaine) | Les objets propres au client (abonné, livre, commande) et leurs règles : « un abonné ne peut pas réserver plus de 3 livres ». | d'affichage, de SQL, de dépendance à une page web |
| Persistance | Savoir comment accéder aux données : connexion à la base, requêtes, lecture d'un fichier XML, appel d'un service externe. | de règles de gestion |
| Base de données | Le système de gestion de base de données lui-même : tables, contraintes, index, et parfois des morceaux du projet (déclencheurs, procédures stockées). | de logique cachée qu'aucun document ne décrit |
Le mot « contrôleur » est ici pris au sens de la logique des cas d'utilisation. Dans un framework web de type MVC, cette logique est souvent répartie entre un contrôleur HTTP très fin (il lit la requête et choisit la réponse) et des classes de service.
La règle des dépendances
C'est le point le plus important de ce découpage : chaque couche ne connaît que celle qui est juste en dessous.
- La frontière ne parle jamais directement à la base de données : elle passe par le contrôleur.
- La couche métier ne sait pas qu'elle est utilisée par un site web, une application mobile ou un script en ligne de commande.
- La persistance ne sait rien de ce qui est affiché.
Cette discipline a des conséquences très concrètes : on peut changer l'apparence d'un écran sans toucher aux règles de gestion, ajouter une API à côté du site en réutilisant le même métier, ou changer de base de données en ne modifiant qu'une couche.
Un exemple : réserver un livre
Dans une bibliothèque municipale, un abonné réserve un livre sur le site. Voici le chemin de la demande :
- La frontière reçoit le clic et l'identifiant du livre, et les transmet au contrôleur.
- Le contrôleur demande à la persistance de charger le livre.
- Il demande à l'objet métier
Abonnede réserver ce livre. Les règles de gestion s'appliquent : le livre est-il disponible ? l'abonné a-t-il atteint sa limite ? - Si la réservation est acceptée, le contrôleur demande à la persistance de l'enregistrer.
- La persistance exécute la requête SQL sur la base de données.
- Le contrôleur renvoie le résultat à la frontière, qui l'affiche.
Le même parcours en PHP, volontairement simplifié (sans constructeurs ni gestion des erreurs techniques) :
<?php
// Frontière : reservation.php (affichage et saisie uniquement)
$resultat = $controleur->reserver((int) $_POST['livre'], $abonneConnecte);
echo htmlspecialchars($resultat);
// Contrôleur : déroule le cas d'utilisation
class ReservationControleur {
public function reserver($idLivre, Abonne $abonne) {
$livre = $this->livres->trouver($idLivre); // persistance
if ($livre === null) {
return 'Livre introuvable.';
}
try {
$abonne->reserver($livre); // règles métier
} catch (RegleMetierException $e) {
return $e->getMessage();
}
$this->reservations->enregistrer($abonne, $livre); // persistance
return 'Réservation enregistrée.';
}
}
// Métier : les règles de gestion, sans SQL ni HTML
class Abonne {
public function reserver(Livre $livre) {
if ($this->nbReservations >= 3) {
throw new RegleMetierException('Trois réservations au maximum.');
}
if (!$livre->estDisponible()) {
throw new RegleMetierException('Ce livre est déjà réservé.');
}
$this->nbReservations++;
$livre->marquerReserve();
}
}
// Persistance : le seul endroit qui connaît le SQL
class LivreRepository {
public function trouver($id) {
$st = $this->pdo->prepare('SELECT * FROM livres WHERE id = ?');
$st->execute(array($id));
$ligne = $st->fetch(PDO::FETCH_ASSOC);
return $ligne ? Livre::depuisLigne($ligne) : null;
}
}
Une variante courante donne aux classes métier la responsabilité de se charger et de se sauvegarder elles-mêmes, comme décrit dans l'article Architecture métier. Le principe reste le même : l'accès aux données doit rester dans un endroit identifiable, et non éparpillé dans les pages.
Les erreurs fréquentes
- Du SQL dans les pages d'affichage. C'est le raccourci le plus tentant, et la première cause de projets difficiles à faire évoluer.
- Des règles de gestion dans la frontière. Une règle codée seulement dans un formulaire (ou en JavaScript) est contournée dès qu'une autre porte d'entrée existe : API, import, script.
- Un contrôleur fourre-tout qui contient toutes les règles. Il devient énorme et ne se réutilise pas. Les règles appartiennent au métier.
- Un métier qui produit de l'affichage : du HTML, des messages mis en forme, ou une dépendance aux variables de la requête web.
- Des couches qui se rappellent mutuellement. Si la persistance appelle le contrôleur, le découpage ne sert plus à rien.
- Une structure de table qui remonte jusqu'à l'écran. Des noms de colonnes dans les gabarits d'affichage lient l'écran à la base : renommer une colonne casse alors les pages.
- De la logique cachée dans la base (déclencheurs, procédures stockées) qui n'est décrite nulle part : à documenter, ou à éviter.
Est-ce adapté à tous les projets ?
Pas toujours. Un script de vingt lignes n'a pas besoin de cinq couches, et un découpage trop poussé alourdit un petit projet. Il se justifie dès qu'un projet doit durer, être testé ou être partagé entre plusieurs développeurs. Gardez au minimum la séparation entre l'affichage, les règles de gestion et l'accès aux données : c'est elle qui apporte l'essentiel du bénéfice.
Le découpage facilite aussi les tests. Le métier se teste sans base de données ni navigateur, avec de simples objets ; la persistance se teste avec une base de test ; le contrôleur, avec un faux accès aux données.
Les couches se reflètent naturellement dans l'arborescence du projet, un dossier par couche ou par module : voir l'article Structure de fichiers d'un projet web.
Le lien avec MVC et les autres architectures
| Approche | Correspondance avec les couches |
|---|---|
| MVC | La vue correspond à la frontière, le modèle au métier et à la persistance, et le contrôleur MVC joue un rôle plus étroit que celui décrit ici : il gère la requête web. |
| Architecture hexagonale, architecture « propre » | Même idée de séparation, avec une dépendance inversée : le métier ne dépend de rien, et c'est la persistance qui dépend du métier, par l'intermédiaire d'une interface. On peut ainsi remplacer la base de données ou l'interface sans toucher aux règles. |
Que vous suiviez l'une ou l'autre, l'idée directrice est la même : séparer ce qui change pour des raisons différentes (l'affichage, les règles du client, le stockage), et faire en sorte que les dépendances aillent dans un seul sens.
Pour aller plus loin
- Architecture métier : les classes d'objets et de collections qui représentent les entités.
- Structure de fichiers d'un projet web : organiser les dossiers d'un projet.
- Le processus de développement 2TUP : où les choix techniques, dont l'architecture, s'inscrivent dans le cycle de développement.