Architecture en couches

Écrit Par :

Catégorie:

Développement avancé

Date :

Partager :

Refactorisation d’un monolithe vers une architecture en couches – Version Actualisée

Refactorisation d’un monolithe vers une architecture en couches

Exercice initial

Pour introduire notre démarche, voici une situation : vous disposez d’un programme regroupant toutes les fonctionnalités dans un seul bloc de code, sans réelle organisation. Les vues (affichage), la logique métier (calculs, règles spécifiques) et l’accès aux données (requêtes SQL, connexion à la base) sont entremêlés.

Votre mission : repérer dans ce « monolithe » quelles sont les parties qui correspondent respectivement à l’interface (présentation), au traitement métier et à la persistance de données. Prenez des notes, car la suite du cours explique comment migrer progressivement vers une architecture plus modulaire.

En d’autres termes, nous allons scinder ce monolithe en couches distinctes : la présentation, la couche de service, la couche métier, et la couche d’accès aux données. Cette séparation offre une meilleure maintenabilité, facilite les tests, et rend le projet prêt pour des évolutions futures (ajout de nouvelles fonctionnalités, changements de base de données, etc.).

1) Qu’est-ce qu’une architecture en couches ?

De nos jours, la plupart des applications professionnelles optent pour une forme de séparation en couches. Il existe plusieurs patrons d’architecture, mais l’objectif commun est de garder le code le plus organisé possible.

Dans le modèle le plus classique, on retrouve :

  • La Couche de présentation – tout ce qui concerne l’interface utilisateur
  • La Couche de service – la « passerelle » qui relie l’UI à la logique métier
  • La Couche métier – où résident les règles, calculs et traitements centraux
  • La Couche d’accès aux données (DAL) – responsable du dialogue avec la base de données

Cette structure est largement utilisée parce qu’elle réduit le couplage et permet d’ajouter ou de remplacer plus facilement certains composants (une base de données différente, un nouveau front-end, etc.).

2) Pourquoi séparer en plusieurs couches ?

Lorsque tout est dans un seul bloc de code, la moindre modification peut avoir des répercussions inattendues. Les tests sont compliqués, et il est difficile pour les équipes de se répartir le travail. Avec une architecture en couches :

  • Couplage minimal : chaque couche ne dépend que de l’interface de la couche inférieure.
  • Évolution maîtrisée : on peut changer la technologie d’une couche sans tout réécrire.
  • Tests isolés : on teste la logique métier sans forcément avoir besoin de lancer l’interface ou la base de données.
  • Travail en équipe : les développeurs front-end et back-end peuvent avancer en parallèle.

Zoom sur la couche de présentation

Cette couche correspond à l’interface visible, qu’elle soit en console, en desktop ou en web. Avec ASP.NET Core, elle comprend souvent les contrôleurs MVC, les vues, les ressources statiques (HTML, CSS, JS) et peut s’appuyer sur des frameworks comme Angular. L’essentiel est de limiter la « logique métier » à ce niveau, pour ne laisser que la gestion des écrans, des formulaires et de l’interaction avec l’utilisateur.

Exemple : un contrôleur MVC qui reçoit une requête, appelle la couche de service pour obtenir les données, puis renvoie la vue au client.

3) Couche de service : le maillon essentiel

La couche de service agit comme un intermédiaire entre l’interface et la logique métier. Elle peut s’implémenter via des Web APIs (REST) ou des bibliothèques partagées. Son rôle est de proposer des méthodes claires (souvent sous forme d’API) pour créer, lire, mettre à jour ou supprimer des entités, sans que la présentation n’ait à connaître les détails de l’implémentation.

À l’ère du microservice, cette couche peut être exposée sous forme de services indépendants, sécurisés par des tokens JWT ou d’autres mécanismes d’authentification (par exemple, CAS dans le cadre d’ASP.NET). Le plus important est de protéger les endpoints sensibles (avec [Authorize]), de tracer les actions (logging) et de gérer les transactions ou la validation des données.

Exemple : un service « DemandeService » qui offre des méthodes CreateRequest, GetRequest, etc. Les contrôleurs ou clients s’y connectent pour manipuler les données, sans se soucier de la logique sous-jacente.

Couche métier : le « cœur » de l’application

C’est ici qu’on retrouve la logique essentielle : règles de calcul, validations complexes, enchaînements d’actions. On peut l’organiser selon différents patterns :

  • Transaction Script : méthodes procédurales simples, chaque méthode correspond à un scénario.
  • Table Module : chaque classe gère une table de la BD pour les opérations CRUD.
  • Active Record : un objet = une ligne en base, on peut faire myObjet.Save(), etc.
  • DDD (Domain Driven Design) : un design centré sur le « domaine » et le langage métier. Parfois plus complexe à mettre en place, mais très adapté aux projets exigeants.

Le but est d’isoler ces règles pour pouvoir tester et faire évoluer le code métier indépendamment du reste.

Couche d’accès aux données (DAL)

Le Data Access Layer s’occupe de toute la partie « persistante » : base de données relationnelle (SQL Server, PostgreSQL), NoSQL, fichiers, etc. Dans le monde .NET, on utilise souvent Entity Framework Core ou Dapper pour simplifier les requêtes. Des patterns tels que Repository et Unit of Work permettent de factoriser la logique d’accès.

Exemple : une interface IRepository<T> pour les opérations CRUD génériques, ou encore un RequestRepository spécialisé qui sait gérer les entités de type « Demande ».

Objectifs et bénéfices d’une telle architecture

En séparant la présentation, le service, le métier et les données, on obtient un code plus modulable. Les changements dans la base n’affectent pas la présentation, et vice versa. Les équipes peuvent s’organiser par couche, et on dispose d’une meilleur extensibilité. Les tests unitaires et fonctionnels deviennent plus simples.

Conclusion : on passe d’un code monolithique, où tout est imbriqué, à un système ordonné, où chaque brique a son rôle. Cela facilite la maintenance à long terme.

Implémentation moderne avec .NET Core

Dans le cadre d’ASP.NET Core, on peut répartir le projet en plusieurs sous-projets :

  • MonAppli.Web : Couche de présentation (Razor pages, Angular, etc.)
  • MonAppli.Service : Web APIs, classes de service exposant la logique pour le front
  • MonAppli.Business : Le cœur métier, règles de validation, calculs, etc.
  • MonAppli.DataAccess : Accès à la BD, Repositories, EF Core, migrations
  • MonAppli.Common : Entités partagées, utilitaires, constantes globales

Exemple concret : un système de gestion de demandes (TMS) dans lequel le locataire soumet un formulaire. Le front (Web) envoie la requête à la couche de service, qui utilise le business pour traiter les données, et enfin passe la main à la DAL pour persister en base.

Approche Active Record et DDD revisitées

Active Record : on mappe chaque classe métier directement à la table correspondante. Entity Framework facilite l’écriture de DbSet<T> qui représente les enregistrements. On peut faire context.MyEntities.Add(obj) et le framework se charge de la persistance.

DDD (Domain Driven Design) : on part de la vision du métier, on crée des Aggregates, des Value Objects, et on ne se focalise pas sur la structure physique des tables. C’est idéal quand le domaine est complexe et amené à évoluer.

Couche d’accès aux données – aperçu avancé

Au-delà des simples requêtes CRUD, la DAL peut gérer les transactions et la communication avec plusieurs sources de données (par ex. microservices externes, API de stockage cloud). L’utilisation de UnitOfWork permet de regrouper des séries d’opérations pour garantir leur cohérence. Les migrations EF Core simplifient la gestion du schéma en base.

Conclusion et pistes d’évolution

En migrant d’un programme monolithique vers une architecture en couches, vous gagnez en robustesse, en testabilité et en modularité. Vous pouvez pousser plus loin la séparation en adoptant des microservices ou en exploitant des conteneurs Docker pour isoler chaque couche.

L’essentiel est de clarifier les rôles : la présentation reste légère, la logique métier est centralisée, et l’accès aux données est encapsulé. Avec .NET Core, vous disposez d’un écosystème moderne (Razor, Blazor, EF Core, etc.) pour implémenter ces principes et construire des applications évolutives et pérennes.