Dans le monde du développement .NET moderne, la conception d'APIs robustes et maintenables est un défi constant. MediatR, une bibliothèque de médiation légère pour .NET, s'est imposée comme une solution élégante pour implémenter le pattern Mediator et structurer efficacement le code des APIs. Cet article explore en détail comment architecturer vos APIs avec MediatR en suivant les meilleures pratiques actuelles.
Comprendre les concepts fondamentaux de MediatR
MediatR repose sur deux concepts essentiels :
- Les requêtes (Requests) : représentent des demandes de données
- Les commandes (Commands) : représentent des actions qui modifient l'état du système
Voici un exemple basique d'implémentation :
// Définition d'une requête
public class GetUserQuery : IRequest<UserDto>
{
public int UserId { get; set; }
}
// Handler associé
public class GetUserQueryHandler : IRequestHandler<GetUserQuery, UserDto>
{
private readonly IUserRepository _repository;
public GetUserQueryHandler(IUserRepository repository)
{
_repository = repository;
}
public async Task<UserDto> Handle(GetUserQuery request, CancellationToken cancellationToken)
{
var user = await _repository.GetByIdAsync(request.UserId);
return new UserDto { Id = user.Id, Name = user.Name };
}
}
Configuration dans ASP.NET Core
L'intégration de MediatR dans un projet ASP.NET Core se fait simplement :
public void ConfigureServices(IServiceCollection services)
{
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Startup).Assembly));
services.AddControllers();
}
Implémentation des Patterns CQRS
MediatR facilite naturellement l'implémentation du pattern CQRS (Command Query Responsibility Segregation) :
// Commande
public class CreateUserCommand : IRequest<int>
{
public string Name { get; set; }
public string Email { get; set; }
}
// Handler de commande
public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, int>
{
private readonly IUserRepository _repository;
public CreateUserCommandHandler(IUserRepository repository)
{
_repository = repository;
}
public async Task<int> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
var user = new User { Name = request.Name, Email = request.Email };
await _repository.AddAsync(user);
return user.Id;
}
}
Gestion des Validations et Comportements
MediatR permet d'implémenter des validations via des Pipeline Behaviors :
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IValidator<TRequest>[] _validators;
public ValidationBehavior(IValidator<TRequest>[] validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
var context = new ValidationContext<TRequest>(request);
var failures = _validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Any())
throw new ValidationException(failures);
return await next();
}
}
Tests Unitaires
Les handlers MediatR sont facilement testables :
public class GetUserQueryHandlerTests
{
[Fact]
public async Task Handle_ValidRequest_ReturnsUserDto()
{
// Arrange
var repository = new Mock<IUserRepository>();
var handler = new GetUserQueryHandler(repository.Object);
var query = new GetUserQuery { UserId = 1 };
repository.Setup(r => r.GetByIdAsync(1))
.ReturnsAsync(new User { Id = 1, Name = "Test User" });
// Act
var result = await handler.Handle(query, CancellationToken.None);
// Assert
Assert.Equal(1, result.Id);
Assert.Equal("Test User", result.Name);
}
}
Bonnes Pratiques et Recommandations
- Gardez vos handlers simples et focalisés sur une seule responsabilité
- Utilisez des DTOs pour les requêtes et les réponses
- Implémentez des validations via FluentValidation
- Utilisez des comportements pour la gestion transversale des préoccupations
- Documentez vos handlers avec des commentaires XML
Gestion des Performances
Pour optimiser les performances :
// Mise en cache avec MediatR
public class CachingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IDistributedCache _cache;
public CachingBehavior(IDistributedCache cache)
{
_cache = cache;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
var cacheKey = $"{typeof(TRequest).Name}_{request.GetHashCode()}";
var cachedResponse = await _cache.GetAsync<TResponse>(cacheKey);
if (cachedResponse != null)
return cachedResponse;
var response = await next();
await _cache.SetAsync(cacheKey, response);
return response;
}
}
Conclusion
MediatR offre une approche élégante et flexible pour structurer les APIs .NET. En suivant les bonnes pratiques et en utilisant les patterns appropriés, vous pouvez créer des applications robustes, maintenables et performantes. Les concepts présentés dans cet article vous permettront de tirer le meilleur parti de cette bibliothèque dans vos projets .NET.
N'oubliez pas de consulter la documentation officielle de MediatR et de rester à jour avec les dernières versions pour profiter des nouvelles fonctionnalités et améliorations.