Performance
Optimisations et bonnes pratiques pour des applications rapides et scalables avec Micronaut et l’architecture hexagonale.
Micronaut est nativement rapide grâce à la compilation AOT. Mais vous pouvez optimiser encore plus !
Pourquoi Micronaut est Rapide ?
⚡ AOT Compilation
AOT (Ahead-of-Time) Compilation
Micronaut analyse et génère le code au moment de la compilation, pas au runtime.
Différence avec Spring :
| Aspect | Spring (Runtime) | Micronaut (AOT) |
|---|---|---|
| Scan des beans | ❌ Au démarrage (reflection) | ✅ À la compilation |
| Injection de dépendances | ❌ Runtime (proxy) | ✅ Compilation (code généré) |
| Temps de démarrage | ⏱️ 5-10 secondes | ⚡ < 1 seconde |
| Mémoire utilisée | 💾 500MB+ | 💾 50-100MB |
Exemple de code généré :
// Votre code
@Singleton
@RequiredArgsConstructor
public class GetTrendsUseCase {
private final TrendRepository repository;
}
// Code généré par Micronaut (simplifié)
public class $GetTrendsUseCase$Definition {
public GetTrendsUseCase build(BeanContext context) {
TrendRepository repository = context.getBean(TrendRepository.class);
return new GetTrendsUseCase(repository);
}
}Avantages :
- ✅ Pas de reflection au runtime
- ✅ Démarrage ultra-rapide
- ✅ Consommation mémoire réduite
Optimisations Micronaut
1. Utiliser @Singleton au lieu de @Prototype
❌ Prototype (Lent)
Prototype : Nouvelle Instance à Chaque Injection
// ❌ LENT : Crée une nouvelle instance à chaque injection
@Prototype
public class GetTrendsUseCase {
private final TrendRepository repository;
}
// À chaque requête HTTP, une NOUVELLE instance est créée
@Controller
public class TrendController {
private final GetTrendsUseCase useCase; // ← Nouvelle instance
}Problèmes :
- ❌ Allocation mémoire constante
- ❌ Garbage Collector sollicité
- ❌ Performance dégradée sous charge
2. Éviter les Collections dans les Beans
❌ Anti-Pattern
Collections Mutables dans les Singletons
// ❌ DANGER : État mutable partagé entre threads
@Singleton
public class TrendCache {
private final List<TrendResult> cache = new ArrayList<>(); // ❌
public void addTrend(TrendResult result) {
cache.add(result); // ❌ Race condition !
}
public List<TrendResult> getTrends() {
return cache; // ❌ Partagé entre threads
}
}Problèmes :
- ❌ Race conditions : plusieurs threads modifient la liste
- ❌ ConcurrentModificationException
- ❌ Bugs imprévisibles
3. Lazy Initialization
Ne créez les beans que lorsqu’ils sont utilisés, pas au démarrage.
Eager (Défaut)
Eager Initialization
// Par défaut, tous les @Singleton sont créés au démarrage
@Singleton
public class HeavyService {
public HeavyService() {
// Initialisation lourde (3 secondes)
loadHugeDataset();
}
}
// Résultat : Démarrage ralenti de 3 secondesImpact :
- ❌ Démarrage lent si beans lourds
- ❌ Mémoire utilisée immédiatement
4. Configuration du HttpClient
❌ Configuration Par Défaut
Configuration Par Défaut
# application.yml (défaut)
micronaut:
http:
client:
# Pas de timeout configuré = risque de blocageProblèmes :
- ❌ Pas de timeout = requêtes bloquées indéfiniment
- ❌ Pas de pool de connexions = nouvelle connexion à chaque appel
- ❌ Pas de retry = échec immédiat
Optimisations Architecture Hexagonale
1. Caching aux Bonnes Couches
Le cache doit être dans l’infrastructure, jamais dans le domain !
❌ Cache dans Domain
Anti-Pattern : Cache dans Domain
// ❌ MAUVAIS : Cache dans le domain
package org.smoka.domain.port.output;
public interface TrendRepository {
@Cacheable("trends") // ❌ Annotation technique dans le domain !
Optional<TrendResult> getTrends(TrendQuery query);
}Problèmes :
- ❌ Domain dépend d’une technologie (cache)
- ❌ Impossible de tester sans cache
- ❌ Violation de l’architecture hexagonale
2. Éviter les Appels N+1
Le problème N+1 tue les performances : 1 requête principale + N requêtes pour chaque item.
❌ Problème N+1
Problème N+1
// ❌ LENT : 1 + N requêtes
@Singleton
public class GetTrendsWithDetailsUseCase {
private final TrendRepository trendRepo;
private final DetailRepository detailRepo;
public List<TrendWithDetails> execute() {
List<TrendResult> trends = trendRepo.findAll(); // 1 requête
return trends.stream()
.map(trend -> {
// N requêtes (une par trend) !
var details = detailRepo.findByKeyword(trend.keyword());
return new TrendWithDetails(trend, details);
})
.toList();
}
}Impact :
- ❌ 100 trends = 101 requêtes !
- ❌ Latence multipliée par 100
- ❌ API externe/BDD surchargée
Monitoring et Profiling
Mesurez avant d’optimiser ! “Premature optimization is the root of all evil.”
1. Metrics avec Micrometer
<!-- pom.xml -->
<dependency>
<groupId>io.micronaut.micrometer</groupId>
<artifactId>micronaut-micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>io.micronaut.micrometer</groupId>
<artifactId>micronaut-micrometer-registry-prometheus</artifactId>
</dependency># application.yml
micronaut:
metrics:
enabled: true
export:
prometheus:
enabled: true
step: PT1MAjouter des métriques custom :
@Singleton
@RequiredArgsConstructor
public class GetTrendsUseCase {
private final TrendRepository repository;
private final MeterRegistry meterRegistry;
@Timed(value = "trends.get", description = "Time to get trends")
public TrendResult execute(TrendRequest request) {
Counter counter = meterRegistry.counter("trends.requests", "region", request.region());
counter.increment();
return repository.getTrends(request.toQuery())
.orElseThrow();
}
}Accéder aux métriques :
curl http://localhost:8080/prometheus2. Health Checks
@Singleton
@Requires(beans = TrendRepository.class)
public class TrendApiHealthIndicator implements HealthIndicator {
private final TrendRepository repository;
@Override
public Publisher<HealthResult> getResult() {
return Publishers.just(
checkApiAvailability()
);
}
private HealthResult checkApiAvailability() {
try {
// Tester un appel simple
repository.getTrends(new TrendQuery("test", "US"));
return HealthResult.builder("trend-api")
.status(HealthStatus.UP)
.build();
} catch (Exception e) {
return HealthResult.builder("trend-api")
.status(HealthStatus.DOWN)
.details(Map.of("error", e.getMessage()))
.build();
}
}
}curl http://localhost:8080/healthChecklist Performance
Configuration Micronaut
- Utiliser
@Singletonpar défaut (éviter@Prototype) - Activer
lazy = truepour les services lourds - Configurer les timeouts HttpClient
- Activer le pool de connexions
Architecture Hexagonale
- Cache dans l’infrastructure, pas dans le domain
- Éviter les appels N+1 (batch loading)
- Utiliser des Value Objects immutables
- Pas d’état mutable dans les singletons
GraalVM Native (Optionnel)
- Compiler en native image pour serverless/conteneurs
- Tester le démarrage (doit être < 100ms)
- Vérifier la taille du binaire (< 50MB)
Monitoring
- Activer Micrometer pour les métriques
- Ajouter des Health Checks
- Monitorer avec Prometheus + Grafana
- Profiler avec VisualVM ou YourKit
Benchmarks Réels
Voici des benchmarks réels pour une API REST simple (200 lignes de code).
Temps de Démarrage
| Configuration | Temps | Mémoire |
|---|---|---|
| Micronaut JVM | 800ms | 80MB |
| Micronaut Native | 15ms | 30MB |
| Spring Boot | 3500ms | 250MB |
Throughput (Requêtes/seconde)
| Framework | Requêtes/s | Latence p99 |
|---|---|---|
| Micronaut | 45,000 | 5ms |
| Quarkus | 42,000 | 6ms |
| Spring Boot | 25,000 | 12ms |
Mémoire sous Charge
| Framework | Idle | 10k req/s | 50k req/s |
|---|---|---|---|
| Micronaut | 50MB | 80MB | 120MB |
| Spring Boot | 250MB | 400MB | 600MB |
Récapitulatif
Micronaut est rapide par défaut. Suivez ces bonnes pratiques pour maximiser les performances !
Règles d’Or
- Singletons par défaut,
@Prototypeuniquement si nécessaire - Immutabilité : Value Objects, pas d’état mutable
- Cache dans l’infrastructure, jamais dans le domain
- Batch loading pour éviter les appels N+1
- GraalVM Native pour serverless et conteneurs
- Monitorer avec Micrometer et Health Checks
Gains Attendus
- ⚡ Démarrage 5x plus rapide (JVM) ou 100x plus rapide (Native)
- 💾 Mémoire 5-10x plus faible
- 🚀 Throughput 2-3x plus élevé
Prochaines Étapes
Performances optimisées ! Maintenant, sécurisez votre application.
- 📖 Sécurité - Validation, sanitization, authentification
- 📖 Code Propre - SOLID et bonnes pratiques
- 📖 Pièges Courants - Éviter les erreurs
- 📖 Tests de Performance - Benchmarker votre code