Chargement...

Architecture en couches et Clean Architecture en C#

Apprenez l’architecture en couches et Clean Architecture en C# pour concevoir des applications maintenables.

À mesure que les projets logiciels grandissent, il devient de plus en plus important de maintenir un code bien organisé et durable. Pour cela, on utilise l’architecture en couches (Layered Architecture) et son évolution moderne, l’architecture propre (Clean Architecture). Ces deux approches offrent une base solide pour gérer les dépendances, séparer les responsabilités et construire des systèmes testables et extensibles.


Qu’est-ce que l’architecture en couches ?

L’architecture en couches est un modèle classique qui divise une application en plusieurs couches avec des responsabilités distinctes. Sa forme la plus courante comprend 3 ou 4 couches :

La direction des dépendances est du haut vers le bas : l’UI dépend du métier, le métier dépend de l’accès aux données, mais pas l’inverse.


Flux classique d’une architecture en couches

Le schéma suivant illustre un exemple simple :


graph TD;
  A[Couche UI] --> B[Couche Métier];
  B --> C[Couche Accès aux Données];
  C --> D[Base de Données];

Cette structure est suffisante pour des systèmes CRUD simples, mais elle devient complexe lorsque les couches sont trop couplées.


Qu’est-ce que la Clean Architecture ?

L’architecture propre (Clean Architecture), proposée par Robert C. Martin (Uncle Bob), oriente les dépendances vers le centre (le domaine). Son objectif est d’isoler complètement les règles métiers du monde extérieur.

Dans cette architecture, les dépendances vont désormais de l’extérieur vers l’intérieur :


graph LR;
  A[UI / API] --> B[Application];
  B --> C[Domaine];
  C --> D[Infrastructure (Externe)];

La différence essentielle : la couche Domaine n’a jamais de dépendance vers les couches externes.


Les couches et leurs responsabilités


Règle de dépendance

Le principe fondamental de la Clean Architecture : les dépendances pointent toujours vers le centre. Les couches externes connaissent les internes, mais les internes ignorent les externes.


// Couche Domaine (aucune dépendance)
public class Order
{
    public int Id { get; set; }
    public decimal Total { get; set; }
    public bool IsApproved { get; private set; }

    public void Approve()
    {
        if (Total <= 0)
            throw new InvalidOperationException("Le montant doit être supérieur à 0.");
        IsApproved = true;
    }
}

// Couche Application
public interface IOrderRepository
{
    Task<Order> GetByIdAsync(int id);
    Task SaveAsync(Order order);
}

public class ApproveOrderUseCase
{
    private readonly IOrderRepository _repository;
    public ApproveOrderUseCase(IOrderRepository repository)
        => _repository = repository;

    public async Task ExecuteAsync(int id)
    {
        var order = await _repository.GetByIdAsync(id);
        order.Approve();
        await _repository.SaveAsync(order);
    }
}

// Couche Infrastructure
public class EfOrderRepository : IOrderRepository
{
    private readonly AppDbContext _context;
    public EfOrderRepository(AppDbContext context) => _context = context;

    public Task<Order> GetByIdAsync(int id) =>
        _context.Orders.FirstOrDefaultAsync(o => o.Id == id);

    public Task SaveAsync(Order order)
    {
        _context.Update(order);
        return _context.SaveChangesAsync();
    }
}

// Couche Présentation (API)
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    private readonly ApproveOrderUseCase _useCase;
    public OrdersController(ApproveOrderUseCase useCase) => _useCase = useCase;

    [HttpPost("{id}/approve")]
    public async Task Approve(int id)
    {
        await _useCase.ExecuteAsync(id);
        return Ok();
    }
}

Comme on peut le voir, la couche Domaine ne connaît aucune implémentation d’infrastructure, et la couche Application ne dépend que des interfaces. Les dépendances peuvent ainsi être remplacées facilement (par ex. EF Core ⇢ Dapper).


Gestion des dépendances entre les couches

Les dépendances sont généralement résolues via l’injection de dépendances (DI) dans la couche Infrastructure ou le Composition Root :


// Program.cs ou Startup.cs
builder.Services.AddScoped<IOrderRepository, EfOrderRepository>();
builder.Services.AddScoped<ApproveOrderUseCase>();

Ainsi, le contrôleur ne connaît que ApproveOrderUseCase sans savoir quelle implémentation du dépôt est utilisée.


Différences entre architecture en couches et Clean Architecture

Caractéristique Architecture en couches classique Clean Architecture
Direction des dépendances De haut en bas De l’extérieur vers l’intérieur (vers le noyau)
Dépendance à la base de données La logique métier dépend de la base de données Le domaine est indépendant de l’accès aux données
Testabilité Difficile Facile (mock d’interfaces)
Évolutivité Couches fortement couplées Dépendances gérées via des abstractions
Maintenance à long terme Devient complexe avec le temps Facile à maintenir, structure modulaire

Exemple concret : application e-commerce

Ci-dessous, un exemple simplifié d’une architecture Clean Architecture appliquée à un système e-commerce :

Ainsi, même si l’interface utilisateur change (API → WPF), les règles métiers (Domain & Application) restent intactes.


Bonnes pratiques


Exemple de structure de projet


MyApp/
├── MyApp.Domain/
│   ├── Entities/
│   ├── Interfaces/
│   └── Exceptions/
├── MyApp.Application/
│   ├── UseCases/
│   ├── Services/
│   └── DTOs/
├── MyApp.Infrastructure/
│   ├── Persistence/
│   └── Repositories/
└── MyApp.API/
    ├── Controllers/
    └── Program.cs

Cette structure correspond à une implémentation conforme à la Clean Architecture pour une solution .NET modulaire et maintenable.


TL;DR

  • Architecture en couches : Modèle classique avec un flux UI → Métier → Données.
  • Clean Architecture : Inverse les dépendances – le cœur est le Domaine.
  • Domain est indépendant, Application gère le flux, Infrastructure gère les systèmes externes.
  • Dependency Injection est la clé de la gestion des dépendances.
  • Chaque couche doit avoir une responsabilité claire, être testable et remplaçable.
  • La Clean Architecture est idéale pour les systèmes durables, maintenables et évolutifs.

Articles connexes