Concepts Micronaut
Guide des concepts fondamentaux de Micronaut Framework pour l’architecture hexagonale.
Micronaut est un framework moderne conçu pour les microservices avec AOT (Ahead-of-Time) compilation et faible empreinte mémoire.
IoC Container & Dependency Injection
Qu’est-ce que l’IoC (Inversion of Control) ?
❌ Sans IoC
Sans IoC - Couplage Fort
// ❌ Sans IoC - couplage fort
public class TrendController {
private GetTrendsUseCase useCase = new GetTrendsUseCase(
new MockTrendRepositoryAdapter()
);
// Problèmes :
// - Impossible de changer l'adapter
// - Difficile à tester
// - Gestion manuelle du cycle de vie
}Avantages de l’IoC
L’IoC (Inversion of Control) délègue la création et la gestion des objets au framework.
- ✅ Testabilité : facile de remplacer les dépendances par des mocks
- ✅ Flexibilité : changer l’implémentation sans toucher au code
- ✅ Découplage : les classes ne se connaissent que par leurs interfaces
- ✅ Gestion du cycle de vie : le container crée et détruit les objets
Les Beans
Un bean est un objet géré par le container IoC de Micronaut.
Cycle de Vie d’un Bean
Le container Micronaut :
Crée l’instance
Au démarrage ou à la première utilisation
Injecte les dépendances
Automatiquement via le constructeur
Gère son cycle de vie
Appelle @PostConstruct après création
Le fournit partout
Disponible pour injection dans toute l’app
Comment Déclarer un Bean ?
@Singleton
Avec @Singleton
Pour les services, repositories, use cases :
@Singleton
@RequiredArgsConstructor
public class GetTrendsUseCase {
private final TrendRepository repository;
public Optional<TrendResult> execute(String keyword) {
return repository.getTrends(new TrendQuery(keyword, "US"));
}
}Caractéristiques :
- ✅ Une seule instance dans toute l’application
- ✅ Créée au démarrage (ou lazy à la première utilisation)
- ✅ Partagée entre toutes les injections
Cas d’usage : Use cases, Services, Repositories (stateless)
Scopes de Beans
Le scope définit le cycle de vie d’un bean.
| Annotation | Cycle de vie | Usage |
|---|---|---|
@Singleton | Une instance unique | Services, repositories, use cases |
@Prototype | Nouvelle instance à chaque injection | Objets avec état |
@RequestScope | Une instance par requête HTTP | Données liées à la requête |
@Infrastructure | Singleton bas niveau | Configurations système |
Injection de Dépendances
✅ Constructor Injection
Constructor Injection (Recommandé)
@Singleton
@RequiredArgsConstructor // Lombok génère le constructeur
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class MyService {
TrendRepository repository; // ✅ Immutable, testable
Logger logger;
}Avantages :
- ✅ Champs immutables (
final) - ✅ Facile à tester (on passe les dépendances au constructeur)
- ✅ Visible dans la signature de la classe
- ✅ Lombok + Micronaut travaillent ensemble
C’est la méthode recommandée dans toute l’architecture hexagonale !
Compilation AOT (Ahead-of-Time)
Différence avec Spring
| Framework | Compilation | Démarrage |
|---|---|---|
| Spring | Reflection runtime | Lent (scan classpath) |
| Micronaut | AOT (compile-time) | Ultra rapide |
Comment ça marche ?
Micronaut analyse vos annotations pendant la compilation :
mvn compile- Micronaut scanne
@Controller,@Singleton, etc. - Génère du bytecode pour l’injection (pas de reflection)
- Crée les métadonnées dans
target/classes/META-INF/
Résultat : Au runtime, pas de scan → démarrage instantané (< 1 seconde)
Vérifier la génération
# Voir les beans générés
ls target/classes/META-INF/services/
cat target/classes/META-INF/services/io.micronaut.inject.BeanDefinitionReferenceMicronaut + Lombok
CRITIQUE : L’ordre des annotation processors dans pom.xml est essentiel !
Le Piège de l’Ordre
Lombok DOIT être déclaré AVANT Micronaut dans le pom.xml :
<annotationProcessorPaths>
<!-- ✅ 1. LOMBOK D'ABORD -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
</path>
<!-- ✅ 2. MICRONAUT APRÈS -->
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-inject-java</artifactId>
<version>${micronaut.version}</version>
</path>
</annotationProcessorPaths>Pourquoi Cet Ordre ?
Phase 1 : Lombok génère le code
Lombok génère les constructeurs, getters, etc.
Phase 2 : Micronaut lit le bytecode
Micronaut voit le constructeur → crée l’injection automatiquement
Si Micronaut passe avant Lombok : il ne voit pas le constructeur généré → échec silencieux !
Combo Gagnant : @RequiredArgsConstructor
@Singleton
@RequiredArgsConstructor // Lombok génère le constructeur
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class GetTrendsUseCase {
TrendRepository repository; // Injecté par Micronaut
Logger logger;
}Controllers REST
Créer un Controller
@Controller("/api/users")
public class UserController {
@Get // GET /api/users
public List<User> list() { ... }
@Get("/{id}") // GET /api/users/123
public User getById(Long id) { ... }
@Post // POST /api/users
public User create(@Body User user) { ... }
@Put("/{id}") // PUT /api/users/123
public User update(Long id, @Body User user) { ... }
@Delete("/{id}") // DELETE /api/users/123
public void delete(Long id) { ... }
}Paramètres de Requête
@QueryValue - Query String
@Get
public String search(
@QueryValue String keyword, // Obligatoire
@QueryValue(defaultValue = "US") String region // Optionnel avec défaut
) {
return "Searching for " + keyword + " in " + region;
}Requête : GET /api/trends?keyword=java®ion=FR
Résultat :
keyword = "java"region = "FR"
@PathVariable - Variable dans le Chemin
@Get("/{id}")
public User getById(Long id) {
// id vient du chemin
}Requête : GET /api/users/123
Résultat : id = 123
@Body - Corps de la Requête
@Post
public User create(@Body User user) {
// user contient les données JSON envoyées
return userService.save(user);
}Requête :
POST /api/users
Content-Type: application/json
{
"name": "Alice",
"email": "alice@example.com"
}Résultat :
user.name = "Alice"user.email = "alice@example.com"
Retour JSON Automatique
Micronaut convertit automatiquement les objets en JSON :
@Get
public TrendResponseDto getTrends() {
return new TrendResponseDto(...); // Devient du JSON automatiquement !
}Résultat HTTP :
{
"keyword": "java",
"region": "FR",
"interestScore": 85
}Validation
Micronaut valide automatiquement les paramètres :
@Controller("/api/trends")
public class TrendController {
@Get
public TrendResponseDto getTrends(
@QueryValue @NotBlank String keyword,
@QueryValue @Pattern(regexp = "[A-Z]{2}") String region
) {
// keyword et region sont validés automatiquement
}
}Si la validation échoue, Micronaut retourne 400 Bad Request automatiquement.
Bonnes Pratiques
Suivez ces bonnes pratiques pour un code maintenable et testable.
1. Constructor Injection
Injection par Constructeur
✅ Recommandé
// ✅ Recommandé
@Singleton
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class MyService {
TrendRepository repository;
}Résumé
Micronaut + Lombok = Combo parfait pour l’architecture hexagonale !
| Concept | Explication |
|---|---|
| Bean | Objet géré par Micronaut |
| @Singleton | Bean unique dans l’app |
| @Controller | Bean exposant des endpoints REST |
| IoC | Le framework gère les dépendances |
| Constructor Injection | Meilleure pratique (avec Lombok) |
| AOT | Compilation ultra rapide (vs Spring) |
| Lombok avant Micronaut | Ordre critique dans pom.xml |
Prochaines Étapes
Maintenant que vous maîtrisez Micronaut, découvrez comment l’appliquer à l’architecture hexagonale.
- Annotations Guide → - Lombok + Micronaut en détail
- Ports & Adapters → - Pattern détaillé avec exemples
- Couche Domain → - Structurer le cœur métier