lundi 2 décembre 2024

📜 Spring Boot :Clé pour maîtriser les précédences des fichiers de propriétés 🚀

 Dans Spring Boot, la gestion des propriétés est essentielle pour créer des applications flexibles et bien configurées. Voici un résumé clair et élégant des règles de précédence, présenté sous forme de tableau, pour vous aider à mieux comprendre et maîtriser ce processus.


🌟 Les Grandes Précédences (Globales)

🏆 Rang🔍 Source📖 Description
1Propriétés par défaut (SpringApplication.setDefaultProperties)Base de départ définie via le code pour toutes les propriétés.
2Annotations @PropertySource dans vos classes @ConfigurationCharge des propriétés externes mais arrive tard (après logging.*).
3Fichiers de configuration (application.properties ou YAML)Cœur des propriétés : globales et spécifiques aux profils.
4Propriétés aléatoires (random.*)Génération dynamique de UUIDs, nombres aléatoires, etc.
5Variables d’environnementConfiguration pratique via votre OS (export SPRING_DATASOURCE_URL=...).
6Propriétés système Java-JVM SYSTEM Param (System.getProperties())Passées via la ligne de commande Java avec -D.
7JNDI (java:comp/env)Attributs JNDI spécifiques (pour conteneurs supportés comme Tomcat).
8Paramètres init du ServletContextValeurs configurées au niveau du contexte servlet.
9Paramètres init du ServletConfigValeurs spécifiques à un servlet particulier.
10Propriétés JSON intégrées (SPRING_APPLICATION_JSON)Configuration JSON inline pour des cas complexes.
11Arguments de ligne de commandePrioritaires et pratiques pour les environnements dynamiques (--server.port=8081).
12Propriétés des tests (dans @SpringBootTest)Définies spécifiquement pour les scénarios de test.
13Annotations @TestPropertySourceSurcharge de propriétés dans les tests unitaires.
14Paramètres globaux DevToolsDisponible dans $HOME/.config/spring-boot uniquement si DevTools est actif.

🛠️ Précédences dans les Fichiers application.properties

🏆 Rang📁 Type de fichier📖 Description
1Fichiers internes au JAR (application.properties)Contient les configurations par défaut packagées dans votre application.
2Fichiers spécifiques au profil internes au JAR (application-{profile}.properties)Chargés en fonction du profil actif (ex.: dev, prod).
3Fichiers externes au JAR (application.properties)Surchargent les fichiers internes.
4Fichiers spécifiques au profil externes au JAR (application-{profile}.properties)Plus haut niveau : adaptées aux environnements externes (cloud, conteneurs).

💡 Pourquoi c’est important ?

Comprendre cet ordre permet de :

  • Gérer vos configurations efficacement.
  • Définir des priorités entre vos environnements (local, dev, prod).
  • Éviter les conflits de configuration et les surprises en production.

Avec ce tableau en main, vous avez toutes les clés pour devenir un maître de la configuration Spring Boot ! 🎯

dimanche 24 novembre 2024

[VueJS] [Composition API] transmettre et recevoir des données aux composants enfants (enfant <--> parent)

 Avec la Composition API de Vue.js, la gestion des props, des événements et des données suit un modèle différent, plus structuré et modulaire, qui permet de mieux organiser le code dans les composants. Voici les principaux changements pour la gestion des props et des événements avec la Composition API.




1. Gestion des props avec la Composition API

Avec Options API :

Dans l’Options API, les props sont définies dans l'objet props, et on y accède directement via this :

export default { props: { enPromotion: { type: Boolean, required: true } }, template: `<div v-show="enPromotion">🏷️ Produit en promotion !</div>` };

Avec Composition API :

Les props sont passées en tant qu’argument à la fonction setup(), où elles deviennent des objets réactifs (props).

Code équivalent :

<script setup> defineProps({ enPromotion: { type: Boolean, required: true } }); </script> <template> <div v-show="enPromotion">🏷️ Produit en promotion !</div> </template>

2. Transmission de données via props (parent → enfant)

Le fonctionnement est similaire entre Options API et Composition API. Cependant, avec la Composition API :

  • Définir les props est plus explicite grâce à defineProps.
  • Le code devient plus lisible et modulaire, car tout est géré dans setup ou via <script setup>.

Exemple complet (Parent → Enfant) :

Composant parent :

<template> <product-component :enPromotion="true" /> </template>

Composant enfant :

<script setup> defineProps({ enPromotion: Boolean }); </script> <template> <div v-show="enPromotion">🏷️ Produit en promotion !</div> </template>

3. Gestion des événements avec la Composition API

Avec Options API :

Dans l’Options API, on émet un événement avec this.$emit, et le parent écoute cet événement dans le template :

methods: { notifyParent() { this.$emit('add-to-cart', this.productId); } }

Avec Composition API :

Les événements sont émis avec la fonction emit, passée en tant qu'argument dans setup.

Code équivalent :

<script setup> import { defineEmits } from 'vue'; const emit = defineEmits(['add-to-cart']); function notifyParent(productId) { emit('add-to-cart', productId); } </script> <template> <button @click="notifyParent(123)">Ajouter au panier</button> </template>

4. Transmission des données avec des événements (enfant → parent)

Avec la Composition API, l'émission d'un événement reste similaire, mais le système est plus clair grâce à defineEmits. On peut également documenter explicitement les événements émis par le composant.

Exemple complet (Enfant → Parent) :

Composant enfant :

<script setup> defineEmits(['add-to-cart']); function ajouterProduit(productId) { emit('add-to-cart', productId); } </script> <template> <button @click="ajouterProduit(123)">Ajouter au panier</button> </template>

Composant parent :

<template> <product-component @add-to-cart="mettreDansPanier" /> </template> <script setup> function mettreDansPanier(productId) { console.log('Produit ajouté au panier :', productId); } </script>

5. Avantages de la Composition API

  • Meilleure organisation du code : Toutes les fonctionnalités liées à une fonctionnalité (props, événements, données) sont regroupées dans setup, ce qui simplifie les composants complexes.

  • Réutilisabilité accrue : Le code peut facilement être déplacé dans des composables (fonctions réutilisables), ce qui améliore la modularité.

  • Clarté explicite : Les fonctions comme defineProps et defineEmits documentent directement le contrat entre un composant et ses parents/enfants.


En résumé :

Avec la Composition API :

  • Les props sont définies avec defineProps et utilisées directement dans le template sans avoir besoin de this.
  • Les événements sont gérés avec defineEmits, ce qui rend leur usage plus explicite et documenté.
  • Le code est plus structuré et réutilisable, particulièrement utile pour les projets complexes. https://fr.vuejs.org/api/sfc-script-setup#defineprops-defineemits 

[VueJs] Transmettre des données aux composants enfants avec les props

Comment un composant parent peut transmettre des données à un composant enfant grâce aux props, en utilisant une propriété indiquant si un produit est en promotion.






1. Problème initial :

  • L'application est composée de deux composants principaux :
    • Composant Parent : Gère l'ensemble de la page et contient des données globales comme celles liées au panier.
    • Composant Enfant : Affiche les détails du produit, comme sa description ou des badges spécifiques.
  • Le composant enfant ne peut pas accéder directement aux données du parent.

2. Solution : Utiliser des props :

  • Les props permettent au composant parent de transmettre des données spécifiques à son enfant.

3. Étapes pour utiliser les props :

a) Déclarer une donnée dans le parent :

  • Dans le composant parent, on crée une donnée enPromotionProduct qui indique si le produit est en promotion.
data() { return { enPromotionProduct: true // Par défaut, le produit est en promotion }; }

b) Passer cette donnée au composant enfant :

  • Dans le template du parent, on transmet la donnée via un attribut personnalisé :
  • <product-component :enPromotion="enPromotionProduct"></product-component>

c) Déclarer et utiliser la prop dans l'enfant :

  1. Déclarer la prop dans le composant enfant pour qu'il puisse recevoir et utiliser cette donnée :
    props: { enPromotion: { type: Boolean, // La prop doit être de type booléen required: true // Cette prop est obligatoire } }
  2. Utiliser la prop dans le template du composant enfant. Par exemple, afficher un badge "Promotion" si la prop est vraie :
    <div v-show="enPromotion" class="promotion-badge"> 🏷️ Produit en promotion ! </div>

4. Résultat final :

  • Lorsque la donnée enPromotionProduct dans le parent est true, un badge "🏷️ Produit en promotion !" s'affiche dans le composant enfant.
  • Si cette donnée est modifiée dans le parent (par exemple, false), le composant enfant met automatiquement à jour son rendu, et le badge disparaît.

Points importants :

  1. Unidirectionnalité : Les props permettent un flux de données clair du parent → enfant.
  2. Validation des props : On peut préciser le type, rendre la prop obligatoire, ou même définir des valeurs par défaut.
  3. Dynamisme : Toute modification de la donnée dans le parent se répercute automatiquement sur l’enfant.

Avantages de l'exemple enPromotion :

Cet exemple montre clairement comment un composant parent peut contrôler le rendu du composant enfant de manière conditionnelle. Cela illustre aussi un cas d'usage réaliste pour des sites e-commerce, où des informations comme l'état de promotion d'un produit sont essentielles.

[VueJs] Émettre des événements depuis un composant enfant

 Comment émettre des événements personnalisés depuis un composant enfant pour communiquer avec un composant parent.



Voici les étapes clés :

  1. Problème initial :

    • L'application est structurée en deux composants :
      • Parent : Contient la page globale et le panier.
      • Enfant : Contient la description du produit.
    • En déplaçant le bouton "Ajouter" dans le composant enfant, le panier ne fonctionne plus, car il dépend d'une action dans le parent.
  2. Émission d’un événement :

    • Dans le composant enfant :
      • On utilise this.$emit('nom-de-l-evenement') pour informer le parent qu’une action s’est produite.
      • Exemple : Lorsqu'un utilisateur clique sur "Ajouter", l'événement personnalisé add-product est émis.
  3. Écoute par le parent :

    • Dans le composant parent :
      • On ajoute un écouteur d’événement avec @add-product="methodeParent".
      • Cette méthode implémente la logique, comme mettre à jour le panier.
  4. Transmission de données :

    • L'événement peut transporter des données via un second argument
    • Exemple :
      • this.$emit('add-product', price) dans l'enfant.
    • Dans le parent, la méthode liée peut récupérer ces données via $event.
      Exemple : addCard(price) { this.totalPrice += price; }
  5. Résultat final :

    • En cliquant sur "Ajouter", le composant enfant émet un événement avec le prix du produit.
    • Le parent écoute cet événement, récupère les données et met à jour le panier (quantité et total des prix).

Points importants :

  • Événements personnalisés sont une solution efficace pour la communication enfant → parent.
  • Transport de données : Les événements peuvent inclure des arguments pour transmettre des informations au parent.
  • Flexibilité : Ce mécanisme permet de maintenir une architecture de composants propre et modulaire.

vendredi 25 octobre 2024

[Spring boot] Problème rencontré avec `ddl-auto` par défaut dans les tests Hibernate

Lorsque nous exécutons nos tests Spring, nous avons remarqué qu’Hibernate **supprime** (drop) et **recrée** les tables à chaque exécution. Ce comportement entraîne la perte de données après chaque test.


Cela est dû à la configuration par défaut de Spring Boot : si une **base de données embarquée** (comme H2) est détectée et qu’aucun outil de gestion de schéma (par exemple Flyway ou Liquibase) n'est utilisé, alors `spring.jpa.hibernate.ddl-auto` passe automatiquement à `create-drop`. Ce mode initialise le schéma à chaque exécution, ce qui peut ne pas être souhaité en test ou en production.


**Documentation de référence :**

[Spring Boot Data Initialization](https://docs.spring.io/spring-boot/how-to/data-initialization.html)


---


### Solution : Configuration pour éviter la suppression du schéma dans les tests et en production

Ou bien utiliser CommandLineRunner pour peupler la BDD avant que spring ne soit prêt à recevoir des requêtes

1. **Configurer pour les tests** :

   Si vous souhaitez garder une base de données H2 pour les tests sans que les données soient perdues à chaque exécution, configurez `ddl-auto` comme suit :


   ```yaml

   spring:

     jpa:

       hibernate:

         ddl-auto: update  # Ne supprime rien, mais adapte la structure au besoin

     datasource:

       platform: h2

       url: jdbc:h2:./test-h2/test-db;DB_CLOSE_DELAY=-1;MODE=Oracle;TRACE_LEVEL_FILE=4;AUTO_SERVER=TRUE

       driver-class-name: org.h2.Driver

       username: sa

       password:

     h2:

       console:

         path: /manage/h2/console

         enabled: true

     jpa:

       properties:

         hibernate:

           dialect: org.hibernate.dialect.H2Dialect

   ```


   Cette configuration empêche Hibernate de supprimer les tables, mais permet toujours de mettre à jour leur structure si des changements sont détectés.


2. **Configurer pour la production** :

   En production, il est recommandé de **désactiver** `ddl-auto` ou de le configurer en mode `validate` pour empêcher Hibernate de modifier le schéma automatiquement. Utilisez des outils de migration de schéma comme **Flyway** ou **Liquibase** pour gérer les modifications.


   ```yaml

   spring:

     jpa:

       hibernate:

         ddl-auto: none

   ```


   Cette configuration garantit que Hibernate n’effectue aucune modification sur les tables SQL en production, laissant le contrôle du schéma aux outils de migration.


---


### Notes importantes


- Par défaut, si la propriété `spring.jpa.hibernate.ddl-auto` n’est pas explicitement définie, Spring Boot utilise `create-drop` pour les bases de données embarquées (comme H2).

- En production, il est **fortement recommandé** de définir `ddl-auto` sur `none` ou `validate` pour éviter les manipulations non intentionnelles du schéma.


En résumé, adaptez `ddl-auto` à l’environnement : utilisez `update` pour les tests si vous souhaitez conserver les données, et `none` ou `validate` en production pour un schéma sous contrôle.

jeudi 3 octobre 2024

[Spring] Le bean singleton dans Spring est-il thread-safe ?

 La réponse est non, il n'est pas thread-safe, et c'est pourquoi beaucoup de développeurs préfèrent utiliser l'injection par le constructeur plutôt que par le champ. De cette manière, ils peuvent définir leurs objets singleton comme immuables. Cela commence par rendre les attributs final.



La raison pour laquelle je veux que mes attributs soient immuables est de m'assurer que personne ne les modifie, car un singleton n'est pas synchronisé par Spring de quelque manière que ce soit. Il n'est même pas recommandé de le faire, ne changez jamais les attributs d'un objet singleton. Théoriquement, c'est possible, si pour une raison quelconque vous choisissez de le faire, assurez-vous de gérer la synchronisation vous-même. Vous pouvez utiliser le mot-clé synchronized en Java, vous pouvez utiliser des outils comme les sémaphores, ReadWriteLock, des barrières cycliques, etc.

Autre raccourcis avec Lombok :

@RestController

@RequiredArgsConstructor

public class DemoController {

    private final DemoService demoService;


    @GetMapping("/demo")

    public String getDemo() {

        return demoService.getDemoMessage();

    }

}


dimanche 8 septembre 2024

[Thread] Why was the stop() method on thread deprecated?

Thread. stop is being deprecated because it is inherently unsafe.

https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html#:~:text=Because%20it%20is%20inherently%20unsafe,exception%20propagates%20up%20the%20stack.)

https://docs.oracle.com/cd/E19455-01/806-3461/6jck06gr5/index.html#:~:text=of%20JDK%201.1.-,Thread.,because%20it%20is%20inherently%20unsafe.

 

jeudi 30 mai 2024

How to use Testcontainers in SpringBoot?

Introduction

Les tests d'intégration jouent un rôle crucial dans le développement logiciel moderne. Cependant, les tests avec des mocks ou des services en mémoire peuvent ne pas représenter fidèlement les environnements de production, entraînant des bugs inattendus. C'est là que Testcontainers entre en jeu.

Pourquoi avons-nous besoin de Testcontainers ?

Les Défis des Tests Traditionnels

Les tests traditionnels avec des mocks ou des services en mémoire présentent plusieurs limitations, notamment :

  • Incapacité à simuler des environnements de production réels
  • Fonctionnalités sous-jacentes différentes
  • Difficulté à identifier les bugs en environnement de développement

Les Avantages de Testcontainers

Testcontainers permet d'exécuter des tests d'intégration avec des services réels en utilisant des conteneurs Docker, ce qui :

  • Offre un environnement de test plus proche de la production
  • Réduit les bugs en production
  • Améliore la fiabilité des tests

Prérequis pour Réaliser ce TP

Prérequis Techniques

  1. JDK : Utilisez le JDK 21.0.1 ou une version ultérieure. Téléchargez-le ici.
  2. Maven : Utilisez Maven 3.9.7, la dernière version stable. Téléchargez-le ici.
  3. Docker : Utilisez Docker Engine 24.0.9, la dernière version stable. Téléchargez-le ici.

Prérequis de Connaissances

  1. Java
  2. Spring Boot
  3. JUnit
  4. Docker

Installation des Dépendances

Ajoutez les dépendances suivantes à votre fichier pom.xml pour Testcontainers :

xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-testcontainers</artifactId> <version>1.19.8</version> <!-- Utilisez la dernière version de Testcontainers --> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <version>1.19.8</version> <!-- Assurez-vous que cette version correspond à celle de Testcontainers --> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <version>1.19.8</version> <!-- Utilisez la version de PostgreSQL correspondante --> <scope>test</scope> </dependency>

Cas de Test

Dans l'exemple ci-dessous, nous essayons de tester une API Rest qui crée et récupère les détails des étudiants. La base de données utilisée pour stocker les données des étudiants est PostgreSQL. Avant de commencer, assurez-vous que Docker est en cours d'exécution.

java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Testcontainers public class StudentControllerTest { @Autowired private TestRestTemplate restTemplate; @Autowired private StudentRepository studentRepository; @Container static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>("postgres:15-alpine"); @Test public void testAddNewStudent() { // Given Student student = new Student(); student.setName("Alice"); student.setDateOfBirth("02/02/2021"); student.setGrade("II"); // When ResponseEntity<Student> studentResponseEntity = restTemplate. postForEntity("/students", student, Student.class); // Then Student savedStudent = studentResponseEntity.getBody(); assertEquals(HttpStatus.OK, studentResponseEntity.getStatusCode()); assertNotNull(savedStudent); } @Test public void testFetchStudentById() { // Given Student student = new Student(); student.setName("Bob"); student.setDateOfBirth("03/03/2022"); student.setGrade("III"); studentRepository.save(student); // When ResponseEntity<Student> studentResponseEntity = restTemplate. getForEntity("/students/" + student.getId(), Student.class); // Then Student studentResponse = studentResponseEntity.getBody(); assertEquals(HttpStatus.OK, studentResponseEntity.getStatusCode()); assertNotNull(studentResponse); assertEquals(1, studentResponse.getId()); assertEquals(student.getName(), studentResponse.getName()); assertEquals(student.getDateOfBirth(), studentResponse.getDateOfBirth()); assertEquals(student.getGrade(), studentResponse.getGrade()); } }

Exécution des Tests

Avant d'exécuter les tests, assurez-vous que Docker est en cours d'exécution. Utilisez ensuite la commande suivante dans le répertoire racine de votre projet :

bash
./mvnw test

Annotations et Classes Importantes

  • SpringBootTest : Crée un ApplicationContext et exécute l'environnement web entier utilisé pour le test.
  • webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT : Un port disponible est choisi au hasard chaque fois que le test est exécuté.
  • Testcontainers et Containers : Utilisés pour gérer le cycle de vie de démarrage et d'arrêt des conteneurs.
  • TestRestTemplate : De Spring, utilisé pour appeler et tester les points de terminaison REST à travers les cas de test.
  • ServiceConnection : Introduit dans SpringBoot 3.1, utilisé pour enregistrer la connexion de la base de données dans le conteneur au lieu de la mentionner manuellement en utilisant l'annotation DynamicPropertySource.
  • PostgreSQLContainer<>("postgres:15-alpine") : Télécharge la version 15 de PostgreSQL basée sur Alpine Linux comme image de base lorsque le conteneur se lance.

Conclusion

En utilisant Testcontainers, vous pouvez améliorer la fiabilité de vos tests d'intégration en exécutant des services réels dans des conteneurs Docker, créant ainsi un environnement de test plus proche de la production. Cela permet de réduire les bugs qui peuvent survenir en production en s'assurant que vos tests sont exécutés dans un environnement similaire. Testcontainers offre une solution puissante pour tester les intégrations avec des services réels, éliminant les limitations des mocks ou des services en mémoire, et contribuant à une meilleure qualité globale du logiciel. Essayez Testcontainers dans vos projets pour découvrir ses avantages par vous-même.

@DataJpaTest and Repository Class in JUnit | Baeldung
Configuring Separate Spring DataSource for Tests | Baeldung

dimanche 19 mai 2024

Why you should avoid returning lists in API?


En tant que développeurs backend, nous passons une grande partie de notre temps à créer des APIs. 

La création d’APIs flexibles et maintenables est cruciale pour assurer la satisfaction des consommateurs et faciliter les évolutions futures. 

Une pratique courante, mais souvent problématique, consiste à renvoyer directement une liste JSON dans les réponses de l’API

Dans cet article, nous allons examiner pourquoi encapsuler les réponses dans un objet JSON est une bonne pratique et comment cela peut améliorer la qualité de votre API.

Flexibilité et Extensibilité

Facilité d’ajout de nouvelles données

Lorsque vous encapsulez la réponse dans un objet JSON, il est beaucoup plus facile d’ajouter de nouvelles informations sans rompre la compatibilité avec les clients existants

Par exemple, si vous souhaitez ajouter des informations de pagination ou des métadonnées, vous pouvez simplement ajouter de nouveaux champs à l’objet JSON.
Cela évite les problèmes de compatibilité rétroactive.

{
  "students": [
    {
      "name": "Fabrice",
      "level": "Expert"
    },
    {
      "name": "Alice",
      "level": "Junior"
    }
  ],
  "totalElements": 2,
  "totalPages": 1,
  "currentPage": 1,
  "links": {
    "self": "/api/v1/students?page=1",
    "next": "/api/v1/students?page=2"
  }
}

Clarté et Structure

Organisation

En encapsulant les données de réponse dans un objet, vous créez une structure claire et bien définie. Cela facilite la compréhension pour les consommateurs de l’API, car ils peuvent facilement naviguer et extraire les données nécessaires. Cette organisation structurée réduit également les risques d’erreurs lors de l’utilisation de l’API.

Gestion des Erreurs et des Métadonnées

Messages d’erreur

Avec un objet JSON, vous pouvez inclure des messages d’erreur, des codes de statut, et d’autres informations pertinentes directement dans la réponse. Cela permet aux clients de gérer les erreurs de manière plus efficace et d’avoir une meilleure compréhension de ce qui ne va pas.

{
  "students": [],
  "error": {
    "code": 404,
    "message": "No students found"
  }
}

Uniformité et Standardisation

Consistance des réponses

En adoptant un format standard pour toutes les réponses, vous assurez une consistance à travers toute l’API. Les clients savent toujours à quoi s’attendre, ce qui facilite le développement et le débogage. Cette uniformité améliore également la documentation de l’API et la compréhension pour les nouveaux développeurs.

Support des Hypermédias (HATEOAS)

HATEOAS

L’approche RESTful préconise l’utilisation de liens hypermédia pour guider les clients sur les actions possibles. Utiliser un objet JSON facilite l’inclusion de tels liens, permettant aux clients de découvrir et d’interagir avec l’API de manière dynamique.

{
  "students": [
    {
      "name": "Alice",
      "level": "Sophomore",
      "links": {
        "self": "/api/v1/students/1",
        "courses": "/api/v1/students/1/courses"
      }
    }
  ],
  "links": {
    "self": "/api/v1/students",
    "next": "/api/v1/students?page=2"
  }
}

Préparation pour les Futures Évolutions

Évolution de l’API

Les APIs évoluent souvent avec le temps. En encapsulant les réponses dans des objets JSON, vous préparez l’API à évoluer sans casser la compatibilité avec les clients existants

Vous pouvez déprécier certains champs et en introduire de nouveaux progressivement, ce qui facilite la gestion des versions.

Conclusion

Utiliser un objet JSON pour encapsuler les réponses d'une API REST offre de nombreux avantages, notamment en termes de flexibilité, de clarté, de gestion des erreurs, et de support des hypermédias. Cette pratique améliore globalement la qualité, la maintenabilité, et la robustesse de votre API, tout en préparant le terrain pour des évolutions futures. En adoptant cette approche, vous assurez une meilleure satisfaction des consommateurs et une plus grande facilité de développement pour les mainteneurs de l'API. 
Adoptez dès maintenant cette pratique pour vos projets et observez la différence en termes de flexibilité et de maintenabilité de vos APIs REST.

dimanche 5 mai 2024

[Java 21 ] “String Templates” previews additional enhancements (JEP 430)


Introduction

Java 21, la version la plus récente de Java avec un support à long terme, a apporté une multitude de nouvelles fonctionnalités. L’une des plus notables est le “String Templates” ou modèles de chaînes de caractères.

Le défi

Dans de nombreux langages de programmation, l’interpolation de chaînes de caractères est couramment utilisée pour insérer des valeurs dynamiques dans des chaînes de caractères. Cependant, cette méthode peut présenter des risques de sécurité. De plus, la concaténation de chaînes de caractères peut rendre le code difficile à lire et à comprendre. Il existe également d’autres vulnérabilités potentielles liées à l’interpolation de chaînes de caractères, comme les attaques de format de chaîne et les vulnérabilités d’interpolation de chaînes de caractères dans Apache Commons Text.

La composition de chaînes de caractères en Java

En programmation, la composition de chaînes de caractères est une tâche courante. En Java, il existe plusieurs méthodes pour composer des chaînes de caractères, chacune ayant ses propres avantages et inconvénients. Par exemple, la concaténation de chaînes de caractères est la méthode la plus basique pour construire des chaînes de caractères. Cependant, elle peut rendre le code difficile à lire et à maintenir. D’autres méthodes, comme l’utilisation des classes StringBuilder et StringBuffer, ou des formateurs de chaînes de caractères, ont leurs propres défis.

La solution : Les “String Templates”

Pour résoudre ces problèmes, Java 21 introduit les “String Templates”. Ces modèles permettent d’insérer des valeurs dans des chaînes de caractères tout en assurant leur validation et leur assainissement. Cela rend le code plus sûr et plus facile à lire.

Les “String Templates” ont été introduits dans Java avec plusieurs objectifs en tête :

  • Simplifier le processus d’expression des chaînes de caractères avec des valeurs qui peuvent être compilées au moment de l’exécution.
  • Améliorer la lisibilité des compositions de chaînes de caractères, en surmontant la verbosité associée aux classes StringBuilder et StringBuffer.
  • Surmonter les problèmes de sécurité des techniques d’interpolation de chaînes de caractères que d’autres langages de programmation permettent, en échangeant une petite quantité d’inconvénient.
  • Permettre aux bibliothèques Java de définir une syntaxe de formatage personnalisée du littéral de chaîne de caractères résultant.

Voici quelques exemples d’utilisation des “String Templates” avec différents processeurs de template :

// Utilisation du processeur de template STR pour une salutation personnalisée
String prenom = "Alice";
String salutation = STR."Bonjour, \\{prenom} ! Comment ça va ?";
System.out.println(salutation);  // Affiche : Bonjour, Alice ! Comment ça va ?

// Utilisation du processeur de template FMT pour formater un nombre décimal
double pi = 3.14159;
String piFormat = FMT."La valeur de pi arrondie à deux décimales est \\{%.2f pi}";
System.out.println(piFormat);  // Affiche : La valeur de pi arrondie à deux décimales est 3.14

// Utilisation du processeur de template RAW pour échapper les caractères spéciaux
String texte = "Ceci est un texte avec des caractères spéciaux : \\{, \\}, \\";
String texteEchappe = RAW."\\{texte}";
System.out.println(texteEchappe);  // Affiche : Ceci est un texte avec des caractères spéciaux : \\{, \\}, \\

Discussion

Les “String Templates” sont une fonctionnalité puissante, mais elles sont encore en prévisualisation et pourraient être modifiées dans les versions futures de Java. Cependant, elles ont le potentiel d’améliorer la lisibilité et la sécurité du code Java.

String Templates in Java 21 | Baeldung 

dimanche 28 avril 2024

Transition de RestTemplate à WebClient dans Spring Boot : Un Guide Détaillé

Introduction

Avec l’évolution de Spring Boot et de son écosystème, les développeurs se retrouvent souvent confrontés à la tâche de passer de technologies plus anciennes à des technologies plus récentes et plus efficaces. Une transition qui a gagné en popularité est le passage de RestTemplate à WebClient pour effectuer des requêtes HTTP dans les applications Spring Boot.
RestTemplate a été la solution de choix pour l’accès HTTP côté client pendant de nombreuses années, offrant une API bloquante et synchrone.
Cependant, avec le besoin croissant de programmation réactive et non bloquante pour gérer la concurrence avec moins de ressources, RestTemplate a montré ses limites.


Problème

RestTemplate est un client bloquant et synchrone (the RestTemplate class is in maintenance mode).

Cela signifie que le thread qui exécute la requête se bloque jusqu’à ce que l’opération soit terminée, ce qui peut potentiellement conduire à l’épuisement du pool de threads et à une latence plus élevée sous une charge lourde.
De plus, RestTemplate ne supporte pas la programmation réactive, une nécessité croissante dans les écosystèmes basés sur le cloud.
Cela devient particulièrement apparent dans les architectures de microservices où les ressources doivent être utilisées de manière efficiente.

Pros :

  • Bibliothèque de client HTTP sous-jacente interchangeable
  • Supporte une interface HTTP déclarative
  • Hautement configurable
  • Utilisable dans les anciennes versions du framework Spring

Cons :

  • Avoir plusieurs surcharges d'une méthode rend cette bibliothèque difficile à utiliser
  • Le modèle classique de template Spring est démodé
  • Non adapté aux environnements non-bloquants (par exemple, WebFlux)



Solution : WebClient

WebClient, introduit avec Spring 5, est recommandé comme le successeur de RestTemplate. 

WebClient fonctionne sur un paradigme non bloquant et réactif en utilisant Project Reactor, lui permettant de gérer la concurrence avec moins de threads et moins de surcharge, améliorant considérablement l’évolutivité et l’utilisation des ressources.
De plus, WebClient offre une intégration transparente avec JSON via la bibliothèque Jackson, similaire à RestTemplate, mais avec des capacités de traitement améliorées.

Étapes de Transition

Étape 1 : Dépendance

La première étape consiste à inclure la dépendance spring-webflux dans votre pom.xml ou build.gradle :

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

Étape 2 : Remplacer RestTemplate

Ensuite, vous devrez remplacer les utilisations de RestTemplate par WebClient dans l’ensemble de votre code. Voici un exemple : Avant :

RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(url, String.class);

Après :

WebClient webClient = WebClient.create();
Mono<String> resultMono = webClient.get()
        .uri(url)
        .retrieve()
        .bodyToMono(String.class);
String result = resultMono.block();

Étape 3 : Gestion des Réponses

Étant donné que WebClient est réactif, la gestion des réponses change légèrement.
Au lieu d’obtenir directement le résultat, vous travaillez avec des types réactifs comme Mono et Flux. Cela vous permet d’appliquer des transformations et de composer des opérations asynchrones.
Par exemple :

Mono<String> resultMono = webClient.get()
        .uri(url)
        .retrieve()
        .bodyToMono(String.class);

resultMono.subscribe(result -> {
    // Gérer le résultat
}, error -> {
    // Gérer les erreurs
}, () -> {
    // Gérer la complétion
});

Discussion

La transition de RestTemplate à WebClient peut sembler intimidante au début, mais elle offre de nombreux avantages en termes de performance et d’évolutivité.
Il est important de noter que bien que WebClient soit plus performant, il peut nécessiter un changement de paradigme pour ceux qui sont habitués à l’approche bloquante et synchrone de RestTemplate. Cependant, avec une compréhension claire des concepts de la programmation réactive et une pratique régulière, la transition peut être plus facile que prévu.
Les avantages en termes de scalabilité et de performance sont substantiels, surtout dans les architectures modernes de microservices.
En conclusion, la transition vers WebClient ne consiste pas seulement à suivre les dernières tendances ; c’est aussi une manière plus efficace et évolutive d’effectuer des requêtes HTTP.

https://digma.ai/restclient-vs-webclient-vs-resttemplate/

[Spring Boot ] Migrating from RestTemplate to RestClient

 
The Spring Framework team recommends using RestClient for the new Spring MVC project and also provides guidelines to migrate from RestTemlate to RestClient.

https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#_migrating_from_resttemplate_to_restclient

https://digma.ai/restclient-vs-webclient-vs-resttemplate/

samedi 27 avril 2024

[Java ] Les interfaces fonctionnelles Java pour inverser les dépendances

Avez-vous déjà envisagé d’utiliser les interfaces fonctionnelles Java pour inverser les dépendances dans vos projets Java ?
Dans cet article, nous allons explorer comment nous pouvons le faire en utilisant trois interfaces clés : Supplier, Consumer et Function.

Supplier
L’interface Supplier est utilisée lorsque vous devez fournir un objet sans nécessiter de paramètres d’entrée.

Voici l’interface Supplier :

public interface Supplier<T>{
   T get();
}

Pour mieux comprendre la nécessité d’utiliser cette interface, jetons un coup d’œil à un peu de code :

public class Logger{
   public void log(String message){
      if(isLogEnabled()){
        write(message);
      }
   }
}

// Utilisation de la classe Logger
public class Controller{
   @Inject 
    Logger logger;

   public void execute(){
      logger.log(generateLogMessage());
   }
}

Dans le code ci-dessus, nous avons une classe Logger responsable de l’écriture des messages de log si la journalisation est activée.
La classe Controller invoque le logger en passant le résultat de la méthode generateLogMessage. Jusqu’à présent, tout semble bien.
Cependant, imaginez si la méthode generateLogMessage implique un traitement lourd ou consomme des ressources significatives, et que la journalisation est désactivée.
Dans de tels cas, des ressources précieuses sont gaspillées car le message de log produit ne sera pas utilisé.

La solution à ce problème est de passer un Supplier à la classe Logger qui renverra le message lorsqu’il est demandé, et le logger appelle simplement la méthode dans le cas où le log est activé, comme suit :

public class Logger{
   public void log(Supplier<String> messageSupplier){
      if(isLogEnabled()){
        write(messageSupplier.get());
      }
   }
}

// Utilisation de la classe Logger
public class Controller{
   @Inject Logger logger;

   public void execute(){
      logger.log(() -> generateLogMessage());
   }
}

Maintenant, la méthode generateLogMessage ne sera exécutée que dans le cas où la méthode get du fournisseur est appelée, puis nous économisons des ressources dans le cas où le log n’est pas activé. De plus, avec ce type de solution utilisant le Supplier, nous avons la flexibilité d’implémenter une logique très complexe pour la journalisation et d’être sûr qu’elle ne sera appelée que lorsque cela est nécessaire.

Function 

L’interface Function vous permet de définir une fonction qui prend un paramètre et produit un résultat. Voici l’interface Function (en omettant certaines méthodes par défaut) :

public interface Function<T, R>{
   R apply(T t);
}

Pour commencer à explorer l’interface Function, examinons une classe responsable du calcul du prix d’un article dans une commande de vente. 

Cette classe prend en entrée des éléments tels que le produit, la quantité et la remise (de 0 à 100) à appliquer :

public class PriceCalculator{
   public BigDecimal calculatePrice(Product product, 
                                    Integer quantity,
                                    BigDecimal discount){
     var grossPrice = product.getUnitPrice()
                             .multiply(BigDecimal.valueOf(quantity));
     var discountAmount = grossPrice.multiply(discount)
                                    .divide(BigDecimal.valueOf(100));
     return grossPrice.minus(discountAmount);
   }
}

// Exemple d'utilisation
var result = priceCalculator(product, 10, BigDecimal.value(10));

Cette classe calcule d’abord le prix brut, applique la remise, puis soustrait celle-ci du prix brut. Maintenant, considérons une nouvelle exigence : effectuer une conversion de devise sur le prix.

Une approche pourrait consister à ajouter directement la logique de conversion de devise à cette classe, introduisant potentiellement des bugs.
Une solution plus robuste consiste à introduire un paramètre Function responsable de la gestion de la conversion de devise.

public class PriceCalculator{
   public BigDecimal calculatePrice(
                        Product product, 
                        Integer quantity, 
                        BigDecimal discount, 
                        Function<BigDecimal,BigDecimal> converterFunction){
     var grossPrice = product.getUnitPrice()
                             .multiply(BigDecimal.valueOf(quantity));
     var discountAmount = grossPrice.multiply(discount)
                                    .divide(BigDecimal.valueOf(100));
     var netPrice = grossPrice.minus(discountAmount);
     return converterFunction.apply(netPrice);
   }
}

// Exemple d'utilisation
var result = priceCalculator(product, 
                             10, 
                             BigDecimal.value(10),
                             netPrice -> netPrice.multiply(CURRENCY_RATE));

L’ajout de cette nouvelle exigence a un impact minimal, et nous avons réussi à inverser la dépendance.
La classe PriceCalculator n’a plus besoin de gérer la conversion de devise ; elle se contente d’appeler la fonction fournie avec le prix net et de renvoyer le résultat.
Cette conception nous permet d’utiliser la même classe PriceCalculator pour convertir n’importe quelle devise sans modifier son code.

Il existe différentes approches pour répondre à cette exigence sans modifier la classe PriceCalculator. Vous pouvez créer une autre classe qui agit comme une façade en appelant la PriceCalculator puis en effectuant la conversion de devise.
En général, c’est une décision de projet de savoir quelle solution suivre.

Consumer 

L’interface Consumer nous permet de définir une fonction qui prend un paramètre, effectue une tâche spécifique et ne renvoie aucune valeur. 

Voici l’interface Consumer (en omettant certaines méthodes par défaut) :

public interface Consumer<T>{
   void accept(T t);
}

Pour voir un exemple de l’interface Consumer en action, examinons cette classe, qui définit certaines informations dans une entité et la sauvegarde dans la base de données :

public class EntitySaver{
   public void create(Entity entity){
      entity.setCreationDate(new Date());
      database.insert(entity);
   }
}

// Exemple d'utilisation
entitySaver.create(entity);

Maintenant, supposons que nous devons notifier d’autres classes chaque fois qu’une entité est créée, mais que nous ne pouvons pas modifier l’interface de la méthode create.
Dans de tels scénarios, nous pouvons mettre en œuvre le modèle de publication-abonnement, en utilisant l’interface Consumer.
Voici comment nous pouvons y parvenir :

public class EntitySaver{
   private List<Consumer<Entity>> consumerList = new ArrayList<>();

   public void register(Consumer<Entity> consumer){
      consumerList.add(consumer);
   }

   public void create(Entity entity){
      entity.setCreationDate(new Date());
      database.insert(entity);
      consumerList.forEach(consumer -> consumer.accept(entity));
   }
}

// Exemple d'utilisation
entitySaver.register(entity -> log.info(entity));
entitySaver.register(entity -> mailerService.notifyUser(entity));
entitySaver.create(entity);

Dans cette mise en œuvre du modèle de publication-abonnement, nous utilisons l’interface Consumer. La classe EntitySaver maintient maintenant une liste de Consumers et comprend une méthode register pour ajouter des consumers à cette liste. 

Alors que l’interface de la méthode create reste inchangée, nous introduisons une seule ligne de code pour “consommer” l’entité créée en invoquant les consumers enregistrés.

Conclusion 

Les interfaces fonctionnelles Java ont été introduites il y a de nombreuses années, et elles ont apporté un grand changement dans la façon dont nous développions en Java. 

Nous pouvons les utiliser comme des fonctions lambda, mais aussi pour inverser les dépendances, et rendre notre code plus propre.