Dans le monde du développement .NET moderne, la création de microservices robustes et maintenables nécessite l'application rigoureuse des principes SOLID combinée à des patterns efficaces. Ce workshop explore l'utilisation de MediatR, une bibliothèque puissante qui facilite l'implémentation du pattern Mediator, en conjonction avec les principes SOLID pour construire des microservices élégants et évolutifs.
Les fondamentaux : SOLID et MediatR
Les principes SOLID constituent la base d'une architecture logicielle saine :
- Single Responsibility Principle (SRP)
- Open/Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
MediatR implémente le pattern Mediator, permettant de découpler les composants et de centraliser la gestion des requêtes/réponses.
Mise en place du projet
// Installation des packages NuGet nécessaires
dotnet add package MediatR
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
Configuration de base
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Enregistrement de MediatR
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Startup).Assembly));
// Configuration des autres services
services.AddControllers();
}
}
Implémentation des Handlers et Commands
// Command
public class CreateOrderCommand : IRequest
{
public string CustomerId { get; set; }
public List Items { get; set; }
}
// Handler
public class CreateOrderHandler : IRequestHandler
{
private readonly IOrderRepository _orderRepository;
public CreateOrderHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public async Task Handle(
CreateOrderCommand request,
CancellationToken cancellationToken)
{
// Logique métier
var order = new Order
{
CustomerId = request.CustomerId,
Items = request.Items
};
await _orderRepository.CreateAsync(order, cancellationToken);
return new OrderResult { OrderId = order.Id };
}
}
Intégration dans les Controllers
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly IMediator _mediator;
public OrdersController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task> CreateOrder(
CreateOrderCommand command)
{
var result = await _mediator.Send(command);
return Ok(result);
}
}
Pipeline Behaviors
Les Pipeline Behaviors permettent d'implémenter des aspects transversaux comme la validation ou la journalisation.
public class LoggingBehavior
: IPipelineBehavior
{
private readonly ILogger> _logger;
public LoggingBehavior(
ILogger> logger)
{
_logger = logger;
}
public async Task Handle(
TRequest request,
RequestHandlerDelegate next,
CancellationToken cancellationToken)
{
_logger.LogInformation($"Handling {typeof(TRequest).Name}");
var response = await next();
_logger.LogInformation($"Handled {typeof(TRequest).Name}");
return response;
}
}
Validation avec FluentValidation
public class CreateOrderCommandValidator
: AbstractValidator
{
public CreateOrderCommandValidator()
{
RuleFor(x => x.CustomerId)
.NotEmpty()
.MaximumLength(50);
RuleFor(x => x.Items)
.NotEmpty()
.Must(items => items.Count <= 100)
.WithMessage("Order cannot contain more than 100 items");
}
}
Tests unitaires
public class CreateOrderHandlerTests
{
private readonly Mock _orderRepositoryMock;
private readonly CreateOrderHandler _handler;
public CreateOrderHandlerTests()
{
_orderRepositoryMock = new Mock();
_handler = new CreateOrderHandler(_orderRepositoryMock.Object);
}
[Fact]
public async Task Handle_ValidCommand_CreatesOrder()
{
// Arrange
var command = new CreateOrderCommand
{
CustomerId = "123",
Items = new List()
};
// Act
var result = await _handler.Handle(
command,
CancellationToken.None);
// Assert
Assert.NotNull(result);
_orderRepositoryMock.Verify(
x => x.CreateAsync(
It.IsAny(),
It.IsAny()),
Times.Once);
}
}
Bonnes pratiques et recommandations
- Gardez les Commands et Queries séparés (CQRS)
- Utilisez des DTOs pour la communication entre les couches
- Implémentez la validation au niveau des Commands
- Utilisez des Pipeline Behaviors pour la logique transversale
- Testez les Handlers de manière isolée
Considérations de performance
Pour optimiser les performances :
- Utilisez le pooling d'objets pour les requêtes fréquentes
- Implémentez le caching approprié
- Évitez les opérations bloquantes dans les Handlers
- Utilisez des types de retour asynchrones
Conclusion
L'utilisation combinée de SOLID et MediatR offre une base solide pour construire des microservices maintenables et évolutifs. Cette approche favorise :
- Une séparation claire des responsabilités
- Une meilleure testabilité
- Une maintenance simplifiée
- Une évolutivité accrue
En suivant ces patterns et pratiques, vous pouvez construire des applications .NET robustes et professionnelles qui résisteront à l'épreuve du temps.