Clean Code Architecture C# : Guide Complet et Questions d'Entretien 2026
Maîtrisez les principes Clean Code et Clean Architecture en C# pour créer des applications .NET maintenables et testables. Préparez vos entretiens techniques avec des exemples pratiques.

Clean Code Architecture en C# combine les principes de Clean Code de Robert C. Martin avec le pattern Clean Architecture pour produire des applications .NET maintenables, testables et évolutives. Les entretiens techniques se concentrent de plus en plus sur ces concepts car ils révèlent comment un candidat pense à la conception logicielle au-delà du simple fait de faire fonctionner le code.
Lorsqu'un recruteur pose des questions sur Clean Architecture, il attend des candidats qu'ils expliquent la règle de dépendance : les dépendances du code source pointent vers l'intérieur, vers les politiques de niveau supérieur. La couche Domain ne connaît rien de l'Infrastructure, et non l'inverse.
Les Principes SOLID comme Fondation du Clean Code C#
Les principes SOLID forment la colonne vertébrale du Clean Code en C#. La documentation officielle Microsoft sur les fondamentaux .NET recommande ces patterns pour les applications d'entreprise. Chaque principe adresse un problème de maintenance spécifique.
Principe de Responsabilité Unique (SRP) : Une classe a une seule raison de changer. Le OrderService ci-dessous gère uniquement le traitement des commandes, déléguant la persistance et les notifications à des composants séparés.
public class OrderService
{
private readonly IOrderRepository _orderRepository;
private readonly INotificationService _notificationService;
public OrderService(
IOrderRepository orderRepository,
INotificationService notificationService)
{
_orderRepository = orderRepository;
_notificationService = notificationService;
}
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
// Validates and creates the domain entity
var order = Order.Create(request.CustomerId, request.Items);
// Persists through the repository abstraction
await _orderRepository.AddAsync(order);
// Notifies through a separate service
await _notificationService.SendOrderConfirmationAsync(order);
return order;
}
}Principe Ouvert/Fermé (OCP) : Les classes restent ouvertes à l'extension mais fermées à la modification. Les nouvelles méthodes de paiement nécessitent de nouvelles classes, pas des modifications aux classes existantes.
public interface IPaymentProcessor
{
string PaymentMethod { get; }
Task<PaymentResult> ProcessAsync(Payment payment);
}
// StripePaymentProcessor.cs
public class StripePaymentProcessor : IPaymentProcessor
{
public string PaymentMethod => "Stripe";
public async Task<PaymentResult> ProcessAsync(Payment payment)
{
// Stripe-specific implementation
var charge = await _stripeClient.CreateChargeAsync(payment.Amount);
return new PaymentResult(charge.Id, charge.Status == "succeeded");
}
}
// PaymentService.cs
public class PaymentService
{
private readonly IEnumerable<IPaymentProcessor> _processors;
public PaymentService(IEnumerable<IPaymentProcessor> processors)
{
_processors = processors;
}
public async Task<PaymentResult> ProcessPaymentAsync(
Payment payment, string method)
{
var processor = _processors
.FirstOrDefault(p => p.PaymentMethod == method)
?? throw new NotSupportedException($"Payment method {method} not supported");
return await processor.ProcessAsync(payment);
}
}L'ajout du support PayPal signifie l'ajout d'une classe PayPalPaymentProcessor. Le PaymentService reste inchangé.
Les Quatre Couches de Clean Architecture en .NET
Clean Architecture organise le code en couches concentriques. Le repository Clean Architecture de Jason Taylor fournit un template .NET largement adopté. Chaque couche a des responsabilités explicites et les dépendances flux vers l'intérieur.
| Couche | Responsabilité | Dépendances |
|---|---|---|
| Domain | Entités, objets valeur, événements domaine | Aucune |
| Application | Cas d'utilisation, DTOs, interfaces | Domain |
| Infrastructure | Base de données, APIs externes, système de fichiers | Application, Domain |
| Presentation | Contrôleurs, vues, endpoints API | Application |
public class Customer
{
public Guid Id { get; private set; }
public string Email { get; private set; }
public CustomerStatus Status { get; private set; }
private Customer() { } // EF Core constructor
public static Customer Create(string email)
{
if (string.IsNullOrWhiteSpace(email))
throw new DomainException("Email cannot be empty");
return new Customer
{
Id = Guid.NewGuid(),
Email = email.ToLowerInvariant(),
Status = CustomerStatus.Active
};
}
public void Deactivate()
{
if (Status == CustomerStatus.Inactive)
throw new DomainException("Customer already inactive");
Status = CustomerStatus.Inactive;
}
}L'entité Domain encapsule les règles métier. Elle valide ses propres invariants et expose le comportement à travers des méthodes, pas des setters.
Les setters privés empêchent le code externe de mettre les entités dans des états invalides. La méthode Deactivate() applique la règle selon laquelle les clients inactifs ne peuvent pas être désactivés à nouveau. Ce pattern apparaît fréquemment dans les implémentations Domain-Driven Design et Clean Architecture.
Patterns d'Injection de Dépendances pour Clean Architecture
L'Injection de Dépendances (DI) permet l'inversion de dépendance requise par Clean Architecture. .NET 10 inclut un conteneur DI intégré qui supporte l'injection par constructeur, les durées de vie scopées et les services à clé introduits dans .NET 8.
var builder = WebApplication.CreateBuilder(args);
// Domain services - typically transient
builder.Services.AddTransient<IOrderValidator, OrderValidator>();
// Application services - scoped per request
builder.Services.AddScoped<IOrderService, OrderService>();
// Infrastructure - scoped to share DbContext
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
// External clients - singleton with HttpClient pooling
builder.Services.AddHttpClient<IPaymentGateway, StripeGateway>(client =>
{
client.BaseAddress = new Uri("https://api.stripe.com/v1/");
});
// Keyed services for multiple implementations (.NET 8+)
builder.Services.AddKeyedScoped<INotificationService, EmailNotificationService>("email");
builder.Services.AddKeyedScoped<INotificationService, SmsNotificationService>("sms");
var app = builder.Build();La couche Application définit les interfaces. La couche Infrastructure les implémente. La couche Presentation (ou racine de composition) connecte le tout.
Pattern Repository avec Entity Framework Core 9
Le pattern Repository abstrait l'accès aux données derrière des interfaces centrées sur le domaine. EF Core 9, livré avec .NET 10, fournit l'ORM sous-jacent.
public interface IOrderRepository
{
Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default);
Task<IReadOnlyList<Order>> GetByCustomerAsync(Guid customerId, CancellationToken ct = default);
Task AddAsync(Order order, CancellationToken ct = default);
void Update(Order order);
}
// Infrastructure/Repositories/OrderRepository.cs
public class OrderRepository : IOrderRepository
{
private readonly ApplicationDbContext _context;
public OrderRepository(ApplicationDbContext context)
{
_context = context;
}
public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default)
{
return await _context.Orders
.Include(o => o.Items)
.FirstOrDefaultAsync(o => o.Id == id, ct);
}
public async Task<IReadOnlyList<Order>> GetByCustomerAsync(
Guid customerId, CancellationToken ct = default)
{
return await _context.Orders
.Where(o => o.CustomerId == customerId)
.OrderByDescending(o => o.CreatedAt)
.ToListAsync(ct);
}
public async Task AddAsync(Order order, CancellationToken ct = default)
{
await _context.Orders.AddAsync(order, ct);
}
public void Update(Order order)
{
_context.Orders.Update(order);
}
}Le repository retourne des entités domaine, pas des DTOs. Le mapping vers les DTOs se fait dans la couche Application, gardant le Domain pur.
Prêt à réussir tes entretiens .NET ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Tests Unitaires des Composants Clean Architecture
Clean Architecture facilite les tests car les dépendances sont injectées à travers des interfaces. xUnit et NSubstitute fournissent le framework de test et la bibliothèque de mock pour .NET.
public class OrderServiceTests
{
private readonly IOrderRepository _orderRepository;
private readonly INotificationService _notificationService;
private readonly OrderService _sut;
public OrderServiceTests()
{
_orderRepository = Substitute.For<IOrderRepository>();
_notificationService = Substitute.For<INotificationService>();
_sut = new OrderService(_orderRepository, _notificationService);
}
[Fact]
public async Task CreateOrderAsync_ValidRequest_PersistsAndNotifies()
{
// Arrange
var request = new CreateOrderRequest(
CustomerId: Guid.NewGuid(),
Items: new[] { new OrderItemDto("SKU-001", 2, 29.99m) });
// Act
var order = await _sut.CreateOrderAsync(request);
// Assert
await _orderRepository.Received(1).AddAsync(Arg.Any<Order>());
await _notificationService.Received(1)
.SendOrderConfirmationAsync(Arg.Is<Order>(o => o.Id == order.Id));
}
[Fact]
public async Task CreateOrderAsync_EmptyItems_ThrowsDomainException()
{
// Arrange
var request = new CreateOrderRequest(
CustomerId: Guid.NewGuid(),
Items: Array.Empty<OrderItemDto>());
// Act & Assert
await Assert.ThrowsAsync<DomainException>(
() => _sut.CreateOrderAsync(request));
}
}Les tests vérifient le comportement, pas l'implémentation. Le _sut (system under test) interagit avec des mocks. Les assertions vérifient que les bonnes méthodes ont été appelées avec les bons arguments.
Questions d'Entretien sur Clean Architecture C#
Les entretiens techniques sondent la compréhension de Clean Architecture à plusieurs niveaux. Ces questions apparaissent dans les entretiens .NET de niveau senior.
Les candidats confondent souvent Clean Architecture avec l'architecture N-tier. La différence clé : dans Clean Architecture, les dépendances pointent vers l'intérieur vers le Domain. Dans N-tier, chaque couche dépend de celle en dessous, rendant le Domain dépendant de l'Infrastructure.
Q : Comment Clean Architecture diffère-t-elle de l'architecture en couches traditionnelle ?
L'architecture en couches traditionnelle a chaque couche dépendant de celle en dessous : Presentation dépend de Business Logic, qui dépend de Data Access. Clean Architecture inverse cela : le Domain n'a aucune dépendance, l'Application dépend du Domain, et l'Infrastructure dépend des deux. Cette inversion signifie que la technologie de base de données peut changer sans toucher aux règles métier.
Q : Quand choisiriez-vous de ne pas utiliser Clean Architecture ?
Clean Architecture ajoute de l'indirection. Pour les applications fortement orientées CRUD sans règles métier complexes, la surcharge dépasse le bénéfice. Une API simple qui proxifie directement les tables de base de données n'a pas besoin de quatre couches. La valeur émerge quand la complexité de la logique métier justifie la séparation.
Q : Comment gérez-vous les préoccupations transversales comme le logging et le caching ?
Deux patterns fonctionnent bien : le pattern decorator et le middleware. Un decorator de cache enveloppe l'interface du repository, implémentant la même interface tout en ajoutant la logique de cache. Le logging utilise typiquement du middleware ou un intercepteur DI qui enveloppe les appels de service sans polluer la logique métier.
public class CachingOrderRepository : IOrderRepository
{
private readonly IOrderRepository _inner;
private readonly IDistributedCache _cache;
public CachingOrderRepository(
IOrderRepository inner,
IDistributedCache cache)
{
_inner = inner;
_cache = cache;
}
public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default)
{
var cacheKey = $"order:{id}";
var cached = await _cache.GetStringAsync(cacheKey, ct);
if (cached is not null)
return JsonSerializer.Deserialize<Order>(cached);
var order = await _inner.GetByIdAsync(id, ct);
if (order is not null)
{
await _cache.SetStringAsync(
cacheKey,
JsonSerializer.Serialize(order),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
},
ct);
}
return order;
}
// Other methods delegate to _inner
}Q : Comment structurez-vous la validation dans Clean Architecture ?
La validation se fait à deux niveaux. La validation domaine (invariants) appartient aux entités : une commande ne peut pas avoir zéro articles. La validation application (validation des entrées) appartient aux handlers de commandes ou aux validateurs : la requête doit inclure un ID client valide. FluentValidation s'intègre bien pour la validation des entrées, tandis que la validation domaine reste dans les constructeurs et méthodes des entités.
Organisation du Code et Conventions de Nommage
La structure du projet communique l'architecture. Le template standard Clean Architecture organise les projets par couche.
src/
├── MyApp.Domain/
│ ├── Entities/
│ ├── ValueObjects/
│ ├── Events/
│ └── Exceptions/
├── MyApp.Application/
│ ├── Common/
│ │ ├── Interfaces/
│ │ └── Behaviors/
│ ├── Orders/
│ │ ├── Commands/
│ │ ├── Queries/
│ │ └── EventHandlers/
│ └── Customers/
├── MyApp.Infrastructure/
│ ├── Persistence/
│ ├── Services/
│ └── Configuration/
└── MyApp.WebApi/
├── Controllers/
├── Filters/
└── Middleware/Les tranches verticales (Orders, Customers) regroupent les cas d'utilisation connexes. Cette organisation scale mieux que les tranches horizontales (Commands, Queries) à mesure que l'application grandit.
Considérations de Performance dans Clean Architecture
L'abstraction a un coût. Chaque appel d'interface ajoute de l'indirection. Ces pratiques minimisent la surcharge tout en préservant la testabilité.
Utiliser des records pour les DTOs : Les records génèrent des implémentations efficaces de Equals et GetHashCode. Ils sont immuables par défaut, empêchant les mutations accidentelles.
public record OrderDto(
Guid Id,
Guid CustomerId,
IReadOnlyList<OrderItemDto> Items,
decimal Total,
DateTime CreatedAt);
public record OrderItemDto(
string Sku,
int Quantity,
decimal UnitPrice);Éviter la sur-abstraction : Chaque classe n'a pas besoin d'une interface. Abstraire les dépendances externes (base de données, clients HTTP, système de fichiers). Les services domaine internes qui n'ont qu'une implémentation ont rarement besoin d'interfaces.
Profiler avant d'optimiser : La surcharge des couches Clean Architecture est typiquement négligeable comparée aux opérations I/O. Une requête base de données prenant 50ms éclipse les microsecondes passées dans le dispatch de méthodes.
Appliquer les Principes Clean Code aux Méthodes C#
Clean Code se concentre sur la lisibilité au niveau des méthodes et des classes. Ces pratiques s'appliquent quel que soit le pattern architectural.
Les méthodes font une seule chose : Une méthode nommée ProcessOrderAndSendEmail viole SRP. La diviser en ProcessOrder et SendOrderConfirmation.
Noms significatifs : CalculateOrderTotal communique l'intention. DoCalculation non. Les noms de variables suivent la même règle : customerOrders plutôt que list.
Méthodes courtes : Si une méthode dépasse 20 lignes, elle fait probablement trop de choses. Extraire des méthodes helper avec des noms descriptifs.
// Before: Long method doing multiple things
public decimal CalculateInvoiceTotal(Invoice invoice)
{
decimal subtotal = 0;
foreach (var item in invoice.Items)
{
subtotal += item.Quantity * item.UnitPrice;
}
decimal discount = 0;
if (invoice.Customer.IsPreferred)
{
discount = subtotal * 0.1m;
}
decimal tax = (subtotal - discount) * 0.2m;
return subtotal - discount + tax;
}
// After: Small methods with single responsibilities
public decimal CalculateInvoiceTotal(Invoice invoice)
{
var subtotal = CalculateSubtotal(invoice.Items);
var discount = CalculateDiscount(subtotal, invoice.Customer);
var tax = CalculateTax(subtotal - discount);
return subtotal - discount + tax;
}
private decimal CalculateSubtotal(IEnumerable<InvoiceItem> items)
=> items.Sum(item => item.Quantity * item.UnitPrice);
private decimal CalculateDiscount(decimal subtotal, Customer customer)
=> customer.IsPreferred ? subtotal * 0.1m : 0;
private decimal CalculateTax(decimal taxableAmount)
=> taxableAmount * 0.2m;La version refactorisée se lit comme un résumé de la logique métier. Chaque méthode helper peut être testée indépendamment.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Points Clés à Retenir pour Clean Code Architecture en C#
- La règle de dépendance : les dépendances du code pointent vers l'intérieur, de l'Infrastructure vers le Domain, jamais vers l'extérieur
- Les principes SOLID guident la conception des classes : responsabilité unique, ouvert à l'extension, substitution de Liskov, ségrégation des interfaces, inversion des dépendances
- Quatre couches séparent les préoccupations : Domain (entités), Application (cas d'utilisation), Infrastructure (systèmes externes), Presentation (API/UI)
- L'injection de dépendances connecte les couches ensemble à la racine de composition, typiquement dans
Program.cs - Le pattern Repository abstrait l'accès aux données derrière des interfaces centrées sur le domaine qui retournent des entités, pas des DTOs
- Les tests unitaires vérifient le comportement en mockant les interfaces, validant que les bonnes méthodes reçoivent les bons arguments
- Clean Code au niveau des méthodes : petites méthodes, noms significatifs, responsabilités uniques
- Le coût de performance de l'abstraction est typiquement négligeable comparé aux opérations I/O ; profiler avant d'optimiser
- Les questions d'entretien sondent la compréhension de la règle de dépendance, les compromis et les patterns pratiques comme les decorators pour les préoccupations transversales
Tu saurais repérer le bug en .NET ?
Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Écrit par
Anthony Fillion-MailletFondateur de SharpSkill
Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.
Mis à jour le 24 août 2026
Tags
Partager
Articles similaires

LINQ avancé en C# en 2026 : Opérateurs, Performance et Questions d'Entretien
Maîtriser les opérateurs LINQ avancés, optimiser les performances des requêtes et se préparer aux questions d'entretien technique C# avec des exemples pratiques.

Clean Architecture avec .NET : Guide pratique
Maîtriser Clean Architecture en .NET avec C#. Découvrez les principes SOLID, la séparation des couches et les patterns d'implémentation pour des applications maintenables.

Questions d'entretien C# et .NET : Guide complet 2026
Les 25 questions d'entretien C# et .NET les plus fréquentes. LINQ, async/await, dependency injection, Entity Framework et bonnes pratiques avec réponses détaillées.