Comment architecturer Dependency Injection efficacement

Découvrez les meilleures pratiques pour structurer l'injection de dépendances et gagner en maintenabilité. Des conseils concrets pour rendre votre code plus modulaire, testable et évolutif.

Olivier Dupuy
05 août 2025

5

Vues

0

Commentaires

3

Min de lecture

L'injection de dépendances (DI) est devenue un pilier fondamental du développement moderne en .NET. Cette pratique, fortement encouragée par Microsoft, permet de créer des applications modulaires, testables et maintenables. Dans cet article, nous allons explorer en détail comment architecturer efficacement l'injection de dépendances dans vos projets .NET.

Les fondamentaux de l'injection de dépendances en .NET

L'injection de dépendances est un pattern d'inversion de contrôle (IoC) qui permet de découpler les composants d'une application. En .NET, le conteneur DI natif fournit trois durées de vie principales pour les services :

  • Transient : Une nouvelle instance est créée à chaque injection
  • Scoped : Une instance par scope (typiquement une requête HTTP)
  • Singleton : Une seule instance partagée pour toute l'application

Configuration de base


public void ConfigureServices(IServiceCollection services)
{
    // Services de base
    services.AddTransient();
    services.AddScoped();
    services.AddSingleton();
}

Architecture en couches avec DI

Une architecture bien pensée sépare les préoccupations en couches distinctes. Voici un exemple d'organisation type :


// Interface de repository
public interface ICustomerRepository
{
    Task GetByIdAsync(int id);
}

// Implémentation du repository public class CustomerRepository : ICustomerRepository { private readonly DbContext _context; public CustomerRepository(DbContext context) { _context = context; } public async Task GetByIdAsync(int id) { return await _context.Customers.FindAsync(id); } }

// Service métier public class CustomerService { private readonly ICustomerRepository _repository; private readonly ILogger _logger; public CustomerService( ICustomerRepository repository, ILogger logger) { _repository = repository; _logger = logger; } }

Bonnes pratiques et patterns avancés

Utilisation du pattern Factory


public interface IServiceFactory
{
    T Create(string type);
}

public class PaymentServiceFactory : IServiceFactory { private readonly IServiceProvider _serviceProvider; public PaymentServiceFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public IPaymentService Create(string type) { return type switch { "stripe" => _serviceProvider.GetService(), "paypal" => _serviceProvider.GetService(), _ => throw new ArgumentException("Type de paiement non supporté") }; } }

Modules d'enregistrement


public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddInfrastructureServices(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        services.AddDbContext(options =>
            options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));
            
        services.AddScoped();
        services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
        
        return services;
    }
}

Gestion des erreurs et validation


public class ServiceResult
{
    public bool Success { get; set; }
    public T Data { get; set; }
    public string Error { get; set; }
}

public class CustomerService { private readonly ICustomerRepository _repository; private readonly IValidator _validator; public async Task> CreateCustomerAsync(Customer customer) { var validationResult = await _validator.ValidateAsync(customer); if (!validationResult.IsValid) { return new ServiceResult { Success = false, Error = string.Join(", ", validationResult.Errors) }; } try { var result = await _repository.CreateAsync(customer); return new ServiceResult { Success = true, Data = result }; } catch (Exception ex) { // Logging... return new ServiceResult { Success = false, Error = "Une erreur est survenue lors de la création du client" }; } } }

Tests unitaires


public class CustomerServiceTests
{
    private readonly Mock _repositoryMock;
    private readonly Mock> _validatorMock;
    private readonly CustomerService _service;
    
    public CustomerServiceTests()
    {
        _repositoryMock = new Mock();
        _validatorMock = new Mock>();
        _service = new CustomerService(_repositoryMock.Object, _validatorMock.Object);
    }
    
    [Fact]
    public async Task CreateCustomer_WithValidData_ReturnsSuccess()
    {
        // Arrange
        var customer = new Customer { Name = "Test" };
        _validatorMock.Setup(v => v.ValidateAsync(It.IsAny()))
            .ReturnsAsync(new ValidationResult());
            
        _repositoryMock.Setup(r => r.CreateAsync(It.IsAny()))
            .ReturnsAsync(customer);
            
        // Act
        var result = await _service.CreateCustomerAsync(customer);
        
        // Assert
        Assert.True(result.Success);
        Assert.Equal(customer, result.Data);
    }
}

Considérations de performance

Pour optimiser les performances de votre application utilisant l'injection de dépendances :

  • Évitez l'injection de dépendances en cascade excessive
  • Utilisez le lifetime approprié pour chaque service
  • Implémentez IDisposable pour libérer les ressources correctement
  • Considérez l'utilisation de Lazy pour le chargement différé


public class ExpensiveService : IDisposable
{
    private bool _disposed;
    private readonly IDbConnection _connection;
    
    public ExpensiveService(IDbConnection connection)
    {
        _connection = connection;
    }
    
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _connection?.Dispose();
            }
            _disposed = true;
        }
    }
    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Conclusion

L'injection de dépendances est un outil puissant qui, bien utilisé, permet de créer des applications .NET robustes et maintenables. Les points clés à retenir sont :

  • Choisir les bons lifetimes pour vos services
  • Organiser votre code en modules cohérents
  • Utiliser des patterns appropriés comme Factory quand nécessaire
  • Implémenter une gestion d'erreurs robuste
  • Écrire des tests unitaires pour valider votre architecture

En suivant ces principes et bonnes pratiques, vous pourrez créer des applications .NET modernes, évolutives et faciles à maintenir.

Partager cet article
42
12

Commentaires (0)

Rejoignez la discussion

Connectez-vous pour partager votre avis et échanger avec la communauté

Première discussion

Soyez le premier à partager votre avis sur cet article !

À propos de l'auteur
Olivier Dupuy

Développeur passionné et contributeur actif de la communauté technique.

Profil
Articles similaires
Navigation rapide
Commentaires (0)