vendredi 24 novembre 2023

[Java 17] Améliorer la conception logicielle avec les sealed classes

 Introduction

Dans le monde de la programmation orientée objet, l’héritage est un concept fondamental qui permet à une classe d’hériter des attributs et des méthodes d’une autre classe. Cependant, le contrôle de l’héritage peut être un défi. C’est là qu’interviennent les classes scellées de Java 17.

Problématique
Dans la conception de logiciels, il est courant de vouloir limiter l’héritage à un ensemble spécifique de classes. Par exemple, vous pouvez avoir une classe OrderState et vouloir que seules certaines classes spécifiques, comme NewOrder, ProcessedOrder, ShippedOrder et DeliveredOrder, puissent en hériter. Sans les classes scellées, n’importe quelle classe pourrait hériter de votre classe, ce qui pourrait conduire à une utilisation non désirée ou imprévue de votre classe.

Solution
Les sealed  classes de Java 17 offrent une solution à ce problème. Une classe scellée est une classe ou une interface qui restreint les autres classes ou interfaces qui peuvent l’étendre. Cela vous permet de contrôler précisément quels états sont possibles pour une commande et d’associer des comportements spécifiques à chaque état. De plus, cela permet au compilateur de vérifier que vous gérez tous les états possibles dans votre code, ce qui peut aider à prévenir les erreurs.

Exemple d’implémentation
Pour illustrer l’utilisation des classes scellées dans la gestion de l’état d’une commande, considérons l’exemple suivant :

public sealed interface OrderState permits NewOrder, ProcessedOrder, ShippedOrder, DeliveredOrder {
    void handle();
}

public final class NewOrder implements OrderState {
    @Override
    public void handle() {
        System.out.println("Handling a new order.");
    }
}

public final class ProcessedOrder implements OrderState {
    @Override
    public void handle() {
        System.out.println("Handling a processed order.");
    }
}

public final class ShippedOrder implements OrderState {
    @Override
    public void handle() {
        System.out.println("Handling a shipped order.");
    }
}

public final class DeliveredOrder implements OrderState {
    @Override
    public void handle() {
        System.out.println("Handling a delivered order.");
    }
}

Dans cet exemple, OrderState est une interface scellée qui peut être implémentée uniquement par les classes NewOrder, ProcessedOrder, ShippedOrder et DeliveredOrder. Chaque classe représente un état différent d’une commande et peut avoir des comportements spécifiques. Par exemple, pour une NewOrder, la méthode handle() pourrait créer une nouvelle entrée dans une base de données, tandis que pour une DeliveredOrder, elle pourrait envoyer un e-mail de confirmation au client.

Discussion
L’utilisation des sealed  classes peut améliorer la sécurité, l’exhaustivité et les performances de votre code. Cependant, elles sont une fonctionnalité relativement récente de Java, introduite en aperçu dans Java 15 et stabilisée dans Java 17. Il est donc possible que de nombreux frameworks et bibliothèques n’aient pas encore adopté cette fonctionnalité. Néanmoins, les sealed  classes offrent un moyen puissant de contrôler l’héritage et peuvent être un outil précieux pour améliorer la conception de vos logiciels.

mercredi 22 novembre 2023

Optimisation du pool de threads dans une application Spring Boot 3.1.1 avec Tomcat

 

Introduction

Les applications Spring Boot sont souvent déployées avec un serveur intégré, comme Tomcat, qui suit le modèle thread-par-requête. Dans ce modèle, chaque requête est traitée par un thread unique provenant d’un pool de threads. La taille de ce pool de threads peut avoir un impact significatif sur les performances de l’application.

Problématique

La configuration par défaut du pool de threads de Tomcat dans une application Spring Boot peut ne pas être optimale pour toutes les situations. Par exemple, la taille par défaut du pool de threads peut atteindre 200 threads avec 10 threads de travail toujours en cours d’exécution. Bien que ces valeurs soient un bon point de départ pour un environnement de production, elles peuvent ne pas être idéales pour un environnement local où l’optimisation de la consommation des ressources est une priorité.

Solution

Spring Boot permet de personnaliser la configuration du pool de threads de Tomcat à l’aide de propriétés spécifiques. Par exemple, vous pouvez définir le nombre maximum de threads de traitement de requêtes via la propriété server.tomcat.threads.max. De plus, vous pouvez définir le nombre minimum de threads qui doivent toujours être en cours d’exécution via la propriété server.tomcat.threads.min-spare.

server:
  port: 9001
  tomcat:
    connection-timeout: 2s
    keep-alive-timeout: 15s
    threads:
      max: 50
      min-spare: 5

Mise à jour pour Spring Boot 3.1.1

Avec la version 3.1.1 de Spring Boot, la configuration du pool de threads reste la même. Cependant, il est important de noter que la configuration optimale du pool de threads peut varier en fonction des spécificités de votre application et de votre environnement. Il est donc recommandé de surveiller régulièrement les performances de votre application et d’ajuster la configuration du pool de threads en conséquence.

Discussion

Déterminer la meilleure configuration pour un pool de threads est complexe et il n’existe pas de formule magique pour le calculer. Une analyse des ressources, une surveillance et de nombreux essais sont généralement nécessaires pour trouver une configuration appropriée. En ajustant ces valeurs, vous pouvez optimiser la consommation des ressources de votre application Spring Boot tout en maintenant de bonnes performances.

Les limites des threads de plateforme

Par défaut, Spring Boot fonctionne avec un pool de threads de 200 threads. Cette valeur peut être modifiée dans les propriétés de l’application pour dire, 500 ou 1000 ou 2000, si vous ajoutez suffisamment de mémoire à votre machine ou VM ou conteneur.
Cependant, si le nombre d’utilisateurs simultanés dépasse cette limite, c’est-à-dire le nombre de threads, certains utilisateurs subiront un problème de performance car ils devront attendre leur tour.

Utilisation de threads virtuels

Nous pouvons surmonter cette limitation en utilisant des threads virtuels au lieu de threads de plateforme pour servir chaque requête utilisateur dans Spring Boot.
En faisant cela, le nombre de threads virtuels qui peuvent fonctionner en parallèle est théoriquement illimité.

jeudi 16 novembre 2023

Redis pour la mise en cache : Comparaison des techniques Write-Behind, Write-Through et Write-Around

 Introduction
Redis est une base de données en mémoire qui est couramment utilisée pour le cache en raison de sa haute performance et de sa flexibilité. Il existe plusieurs techniques de mise en cache avec Redis, notamment le write-behind, le write-through et le write-around.

Problématique
Lors de l’utilisation de Redis pour la mise en cache, un défi majeur est de maintenir la cohérence des données entre le cache et la base de données principale. De plus, il peut y avoir des problèmes de performance lors de l’écriture de données, en particulier lorsqu’il y a un grand nombre d’opérations d’écriture à haute vitesse.

Solutions

  • Write-behind: Dans cette approche, l’application écrit les données uniquement dans Redis, puis Redis met à jour de manière asynchrone la base de données principale. Cela permet d’améliorer les performances d’écriture car les opérations d’écriture sont mises en file d’attente, permettant à l’application de continuer rapidement pendant que le cache rattrape son retard.
  • Write-through: Dans cette méthode, les données sont synchronisées immédiatement entre le cache et la base de données principale. Cela garantit que les données entre le cache et la base de données principale sont toujours cohérentes.
  • Write-around: Cette technique n’est pas mentionnée dans les résultats de la recherche, donc je ne peux pas fournir d’informations à ce sujet.

Discussion
Chaque technique a ses avantages et ses inconvénients. Le write-behind peut améliorer les performances d’écriture, mais il peut y avoir une courte période d’incohérence des données entre le cache et la base de données principale. D’autre part, le write-through garantit la cohérence des données, mais il peut ne pas être aussi rapide que le write-behind car les données sont synchronisées immédiatement

Le choix de la technique dépend des exigences spécifiques de l’application.

https://codeahoy.com/2017/08/11/caching-strategies-and-how-to-choose-the-right-one/   

lundi 30 octobre 2023

[Spring ] La gestion des transactions avec Spring @Transactional Rollback

 Introduction 

Lorsque vous travaillez avec des applications Spring, la gestion des transactions est essentielle pour garantir la cohérence et la fiabilité des opérations de base de données. L’une des questions clés dans ce contexte est de savoir comment gérer les exceptions qui se produisent pendant une transaction. Par défaut, Spring définit une règle de base pour déterminer si une transaction doit être annulée en cas d’exception. 

Cependant, il peut être nécessaire de personnaliser ces règles en fonction de vos besoins spécifiques. C’est là qu’intervient l’attribut “rollbackFor”.

Le Problème

Imaginons que vous travaillez sur une application de commerce électronique qui utilise Spring pour gérer les transactions. Lorsqu’un client passe une commande, plusieurs opérations de base de données doivent être effectuées, comme la mise à jour de l’inventaire, la création d’un nouvel enregistrement de commande et la déduction du solde du client. Si une exception se produit lors de l’une de ces opérations (par exemple, si l’inventaire est insuffisant), vous voulez que toutes les opérations soient annulées pour maintenir la cohérence des données. Cependant, par défaut, Spring ne fait un rollback que pour les exceptions non vérifiées (Unchecked Exception), ce qui peut ne pas être suffisant pour votre cas d’utilisation.

La Solution

Pour résoudre ce problème, vous pouvez utiliser l’attribut “rollbackFor” de l’annotation @Transactional de Spring. Cet attribut vous permet de spécifier les exceptions pour lesquelles un rollback doit être effectué. Par exemple, si vous voulez faire un rollback pour une exception vérifiée (Checked Exception) spécifique (disons InsufficientInventoryException), vous pouvez le faire comme suit :

@Transactional(rollbackFor = InsufficientInventoryException.class)
public void placeOrder(Order order) throws InsufficientInventoryException {
    // Code pour passer la commande
}

Dans cet exemple, si une InsufficientInventoryException est levée lors de l’exécution de la méthode placeOrder(), Spring fera un rollback de toutes les opérations de base de données effectuées dans cette méthode.

Pour tester cette fonctionnalité, vous pouvez utiliser JUnit et Mockito. Par exemple :

@Test
public void whenInsufficientInventory_thenRollback() {
    doThrow(InsufficientInventoryException.class).when(inventoryService).deductInventory(any(Order.class));
    
    assertThrows(InsufficientInventoryException.class, () -> {
        orderService.placeOrder(new Order());
    });
    
    verify(inventoryService, never()).deductInventory(any(Order.class));
}

Dans ce test, nous simulons une InsufficientInventoryException lors de l’appel à inventoryService.deductInventory(). Ensuite, nous vérifions que lorsque cette exception est levée, aucune déduction d’inventaire n’est effectuée.

Discussion

L’utilisation de l’attribut “rollbackFor” offre une grande flexibilité pour gérer les transactions dans Spring. Cependant, il est important de noter que cette approche peut conduire à une logique de gestion des transactions complexe si elle est utilisée sans discernement. Il est recommandé d’utiliser “rollbackFor” uniquement lorsque le comportement par défaut de Spring ne suffit pas.

D’autre part, il convient également de noter que “rollbackFor” ne fonctionne que lorsque la méthode annotée est appelée à partir d’un autre bean Spring. Si elle est appelée à partir du même bean (c’est-à-dire en appelant une autre méthode publique de la même classe), l’annotation @Transactional (et donc “rollbackFor”) sera ignorée.

Un autre concept clé est la propagation des transactions. La propagation détermine comment les transactions sont gérées lorsque vous avez plusieurs méthodes @Transactional imbriquées. Par exemple, si vous avez une méthode A qui appelle une méthode B, et que les deux sont annotées avec @Transactional, comment les transactions sont-elles gérées ? C’est là qu’intervient la propagation.

Par défaut, la propagation est définie sur Propagation.REQUIRED, ce qui signifie que si une transaction est déjà en cours lorsqu’une méthode @Transactional est appelée, cette transaction existante sera utilisée au lieu d’en créer une nouvelle. Cependant, vous pouvez modifier ce comportement en utilisant l’attribut propagation de @Transactional. Par exemple, si vous définissez propagation = Propagation.REQUIRES_NEW, une nouvelle transaction sera toujours créée, même s’il en existe déjà une.

Enfin et surtout, il convient également d’évoquer le rôle des proxies dans la gestion des transactions avec Spring. Lorsque vous utilisez l’annotation @Transactional, Spring crée un proxy autour de l’objet cible. Ce proxy est responsable de la gestion des transactions. Lorsqu’une méthode annotée @Transactional est appelée, le proxy démarre une transaction avant que la méthode ne soit exécutée. Si la méthode se termine normalement, la transaction est validée (commit). Si une exception est levée, la transaction est annulée (rollback). Cependant, lorsque vous appelez une autre méthode publique de la même classe (c’est-à-dire une méthode interne), le proxy est contourné et l’annotation @Transactional est ignorée. Cela signifie que les règles de gestion des transactions que vous avez définies avec @Transactional ne s’appliquent pas aux appels de méthodes internes.

Ces concepts sont essentiels pour comprendre et maîtriser la gestion des transactions dans Spring. Ils vous permettent de contrôler avec précision comment les transactions sont gérées et comment les exceptions affectent le déroulement de vos transactions. En fin de compte, une bonne compréhension de ces concepts vous aidera à construire des applications plus robustes et fiables.

dimanche 29 octobre 2023

[Java 8] Maîtrisez le passage à l’heure d’été et d’hiver avec Java : Un guide pratique pour éviter les pièges du DST

 

Introduction

Le passage à l’heure d’été (Daylight Saving Time, DST) et à l’heure d’hiver est une pratique courante dans de nombreux pays pour profiter au maximum de la lumière du jour pendant les mois d’été et économiser de l’énergie pendant les mois d’hiver.
Cependant, la gestion du DST dans les applications informatiques peut être un défi, en particulier pour les applications qui travaillent avec des dates et des heures.

Problème

Prenons l’exemple d’une application comme Météo France. Cette application utilise des horaires pour fournir des prévisions météorologiques précises à ses utilisateurs. Si elle ne tient pas compte du DST, les prévisions pourraient être décalées d’une heure. Cela signifie que si un utilisateur consulte l’application pour savoir s’il va pleuvoir à 15h00, l’application pourrait en réalité lui montrer la prévision pour 16h00. Si l’utilisateur se fie à cette information pour planifier ses activités, il pourrait se retrouver sous la pluie sans parapluie :) !

De plus, si l’application propose des alertes météorologiques en temps réel, ces alertes pourraient également être décalées d’une heure.
Par exemple, une alerte de tempête prévue pour 18h00 pourrait n’être déclenchée qu’à 19h00, ce qui pourrait mettre les utilisateurs en danger.

Un autre exemple serait une application de réservation de vols.
Si le DST n’est pas correctement géré, les heures de vol affichées pourraient être incorrectes, ce qui pourrait entraîner des confusions et des problèmes pour les voyageurs.

Solution

Pour gérer correctement le DST, vous pouvez utiliser la nouvelle API Date/Time introduite dans Java 8. Par exemple, la classe ZonedDateTime immuable et thread-safe a une prise en charge complète des fuseaux horaires, y compris le DST. Lorsque vous convertissez entre différentes classes de date/heure, la prise en charge du DST est automatiquement prise en compte si vous utilisez correctement l’API.

  • Fuseaux horaires et ZoneId

Le concept de fuseau horaire, inventé au Royaume-Uni, sert à lier une date et une heure à un contexte. Les définitions des différents fuseaux, qui résultent de choix politiques, sont susceptibles de changer au fil du temps. Avec l’ancienne API de manipulation des dates, la classe TimeZone était utilisée pour modéliser le concept de fuseau horaire. Désormais, la nouvelle API se base sur la classe ZoneId qui présente deux différences majeures avec TimeZone.

Tout d’abord, ZoneId est une classe immuable. De plus, les règles liées aux fuseaux sont définies au sein de la classe déléguée ZoneRules et non directement au sein de ZoneId. Un simple appel à la méthode getRules() de ZoneId permet d’obtenir les règles.

  • Offset et ZoneOffset

Un cas d’utilisation classique des fuseaux consiste à définir un offset par rapport au fuseau référence UTC/Greenwich. La classe ZoneOffset, qui hérite de ZoneId, modélise l’offset au niveau heure avec le méridien zéro de Greenwich à Londres.

Voici un exemple de code qui illustre comment le passage à l’heure d’été est géré avec ZonedDateTime :

import java.time.*;

public class Main {
    public static void main(String[] args) {
        // Créer une instance de ZonedDateTime pour une date avant le passage à l'heure d'été
        ZonedDateTime beforeDST = ZonedDateTime.of(2023, 3, 26, 1, 59, 59, 0, ZoneId.of("Europe/Paris"));

        // Ajouter une seconde pour passer à l'heure d'été
        ZonedDateTime afterDST = beforeDST.plusSeconds(1);

        // Afficher les dates et heures avant et après le passage à l'heure d'été
        System.out.println("Avant le passage à l'heure d'été : " + beforeDST);
        System.out.println("Après le passage à l'heure d'été : " + afterDST);
    }
}

Et voici un exemple similaire pour le passage à l’heure d’hiver :

import java.time.*;

public class Main {
    public static void main(String[] args) {
        // Créer une instance de ZonedDateTime pour une date avant le passage à l'heure d'hiver
        ZonedDateTime beforeDST = ZonedDateTime.of(2023, 10, 29, 2, 59, 59, 0, ZoneId.of("Europe/Paris"));

        // Ajouter une seconde pour passer à l'heure d'hiver
        ZonedDateTime afterDST = beforeDST.plusSeconds(1);

        // Afficher les dates et heures avant et après le passage à l'heure d'hiver
        System.out.println("Avant le passage à l'heure d'hiver : " + beforeDST);
        System.out.println("Après le passage à l'heure d'hiver : " + afterDST);
    }
}

Ces exemples affichent les dates et heures avant et après le passage à l’heure d’été et à l’heure d’hiver. Vous remarquerez que lorsqu’une seconde est ajoutée à l’heure juste avant le passage à l’heure d’été ou d’hiver, l’heure saute directement de 1:59:59 à 3:00:00 ou passage à l’heure d’hiver, l’heure saute de 3:00:00 à 2:00:00. C’est-à-dire qu’à 3h00 du matin, l’heure est réglée à 2h00, ce qui signifie qu’il y a une heure supplémentaire dans la journée.

Discussion

En résumé, une mauvaise gestion du DST peut entraîner des erreurs dans les applications qui travaillent avec des dates et des heures. La nouvelle API Date/Time de Java 8 facilite grandement cette tâche.

Il est important de noter que même avec ces outils, la gestion correcte du DST nécessite une attention particulière de la part des développeurs. Les règles du DST peuvent varier d’un pays à l’autre et peuvent changer au fil du temps. Par conséquent, il est essentiel de tester soigneusement votre application pour vous assurer qu’elle gère correctement ces changements.

datetime - Whats the difference between java.util.Date and Zoneddatetime? - Stack Overflow

[SQL] Optimisation des requêtes SQL : Identifier et corriger les mauvaises pratiques

Introduction

L’optimisation des requêtes SQL est un aspect crucial de la gestion des bases de données.
Cependant, certaines pratiques courantes peuvent entraver cette optimisation et entraîner des résultats inattendus.

Problème

Trois problèmes principaux peuvent survenir lors de l’écriture de requêtes SQL, illustrés par les 3 exemples suivants :

  1. Mauvaise pratique : Utilisation de fonctions sur des colonnes indexées dans les prédicats. Exemple : SELECT * FROM employees WHERE YEAR(hire_date) = 1999; 
    Pourquoi c’est une mauvaise pratique ?
    L’utilisation de la fonction YEAR() empêche l’utilisation de l’index sur la colonne hire_date, ce qui peut ralentir la requête.

  2. Mauvaise pratique : Incohérence des types de données dans les prédicats.
    Exemple : SELECT * FROM employees WHERE employee_id = '123';
    Pourquoi c’est une mauvaise pratique ?
    Si vous comparez des colonnes de types de données différents, SQL doit convertir l’un des types pour effectuer la comparaison, ce qui peut également empêcher l’utilisation d’un index.

  3. Mauvaise pratique : Dépendance à un ordre d’évaluation spécifique pour les prédicats dans une clause WHERE.
    Exemple : SELECT * FROM employees WHERE manager_id IS NOT NULL AND manager_id > 0; Pourquoi c’est une mauvaise pratique ?
    SQL n’a pas d’ordre d’évaluation défini pour les prédicats dans une clause WHERE. Par conséquent, si vous vous fiez à un certain ordre d’évaluation, vous pouvez obtenir des résultats inattendus.

Solution

Pour chaque problème identifié, voici une bonne pratique correspondante :

  1. Bonne pratique : Évitez d’utiliser des fonctions sur des colonnes indexées dans les prédicats. Exemple corrigé :
    SELECT * FROM employees WHERE hire_date BETWEEN '1999-01-01' AND '1999-12-31';
    Pourquoi c’est une bonne pratique ? 
    En évitant l’utilisation de fonctions sur des colonnes indexées, nous permettons à SQL d’utiliser l’index existant sur la colonne, ce qui peut améliorer considérablement les performances de la requête.

  2. Bonne pratique : Assurez-vous que les types de données sont cohérents lors de la comparaison de colonnes.
    Exemple corrigé :
    SELECT * FROM employees WHERE employee_id = 123;
    Pourquoi c’est une bonne pratique ? 
    En veillant à ce que les types de données soient cohérents lors de la comparaison, nous évitons que SQL ait à convertir les types, ce qui peut également améliorer les performances et l’exactitude des résultats.

  3. Bonne pratique : Ne vous fiez pas à un ordre d’évaluation spécifique pour les prédicats dans une clause WHERE.
    Exemple corrigé :
    SELECT * FROM employees WHERE manager_id > 0 AND manager_id IS NOT NULL;
    Pourquoi c’est une bonne pratique ?
    En évitant de se fier à un ordre d’évaluation spécifique pour les prédicats, nous nous assurons que notre requête renvoie toujours le résultat attendu, quel que soit l’ordre d’évaluation utilisé par SQL.

Discussion

En identifiant ces mauvaises pratiques et en appliquant les bonnes pratiques correspondantes, vous pouvez optimiser vos requêtes SQL et améliorer les performances de votre base de données. Il est important de continuer à apprendre et à se tenir au courant des meilleures pratiques en matière de SQL pour garantir l’efficacité et la précision de vos requêtes.

samedi 28 octobre 2023

[JAVA 11] Java Optional Best practices


Introduction

Java Optional est un conteneur d’objet qui peut ou non contenir une valeur non nulle. Il offre une alternative plus sûre à null, en évitant les exceptions NullPointerException.

Problème

Avant de plonger dans l’utilisation d’Optional, comprenons quel problème il promet de résoudre. Lorsque nous créons une application Java, nous utiliserons sûrement le chaînage de méthodes pour déréférencer les propriétés des objets, ce qui peut facilement nous faire tomber dans le piège de NullPointerException.

String authorZipCode = book.getAuthor().getAddress().getZipCode();

Dans le fragment de code ci-dessus, il pourrait y avoir une NullPointerException dans n’importe quelle invocation de méthode get.
Par conséquent, la classe Optional résout ce problème en enveloppant l’objet que nous voulons et en le manipulant.

L’utilisation incorrecte de Java Optional peut entraîner des erreurs et une mauvaise performance. Par exemple, déclarer des champs d’une classe ou dans un constructeur avec Optional n’est pas recommandé. De plus, l’appel à Optional.get() sans vérifier au préalable si une valeur est présente peut entraîner une exception NoSuchElementException.

Solution

Au lieu d’utiliser une annotation Java, qui n’est pas vérifiée à la compilation, Optional vous permet d’améliorer vos méthodes et d’indiquer que votre méthode peut retourner des valeurs “vides” (et non null).

Voici quelques bonnes pratiques pour utiliser Java Optional :

  1. Utilisez Optional uniquement pour les retours de méthode : Cela permet d’éviter les erreurs NullPointerException lors de l’appel à des méthodes qui peuvent retourner null.

  2. Initialisez les variables Optional avec empty() au lieu de null : Depuis Java 8, vous pouvez initialiser une variable Optional avec Optional.empty(). Par exemple :

    Optional<String> refMoe = Optional.empty();
    
  3. N’utilisez jamais Optional.get() sans vérifier au préalable si une valeur est présente : Utilisez toujours Optional.isPresent() avant d’appeler Optional.get(). Sinon, vous risquez de rencontrer une exception NoSuchElementException.

  4. Utilisez orElse(null) pour retourner null : Cette méthode doit être utilisée uniquement pour des valeurs fixes comme Collections.empty(), etc.

Discussion sur chaque point


Utilisation de isPresent

Optional<String> optional = getOptional();
if (optional.isPresent()) {
    System.out.println(optional.get());
}

ifPresent et ifPresentOrElse

optional.ifPresent(value -> System.out.println(value));
optional.ifPresentOrElse(
    value -> System.out.println(value),
    () -> System.out.println("Value not present")
);

orElse, orElseGet, orElseThrow

String value = optional.orElse("default");
value = optional.orElseGet(() -> "default");
value = optional.orElseThrow(() -> new RuntimeException("Value not present"));

Optional et les collections

// Mauvaise pratique
Optional<List<String>> optionalList = getOptionalList();

// Bonne pratique
List<String> list = getList(); // Retourne une liste vide si aucune valeur n'est présente

Éviter l’abus d’Optionals

// Mauvaise pratique
Optional<String> optional = Optional.of("value");

// Bonne pratique
String value = "value";

Éviter l’utilisation des Optionals pour les arguments de méthode et les champs

public class MyClass {
    private Optional<String> value; // Mauvaise pratique

    public void myMethod(Optional<String> value) { // Mauvaise pratique
        // ...
    }
}

map et flatMap

Optional<String> optional = getOptional();
optional.map(value -> value.toUpperCase());
optional.flatMap(value -> getAnotherOptional(value));

Éviter l’utilisation excessive de isPresent et get

// Mauvaise pratique
if (optional.isPresent()) {
    String value = optional.get();
}

// Bonne pratique
optional.ifPresent(value -> System.out.println(value));

En suivant ces bonnes pratiques, vous pouvez utiliser Java Optional efficacement et en toute sécurité.

J’espère que ces exemples vous aideront à comprendre comment utiliser efficacement Java Optional. 
😊



[Style de programmation] Réécrire les conditions avec le style "Yoda"

 Problème :
Vous testez les valeurs attendues sur la partie gauche de votre expression.
person.getName() != null && person.getName().equals("name");

Solution : 
Écrivez vos conditions avec la valeur de la variable à gauche et la valeur à tester à droite.
"name".equals(person.getName());

Cela signifie que vous n’avez pas besoin de vérifier si person.getName() est null. Cela économise un peu de frappe et est sans doute plus clair une fois que vous vous y êtes habitué et eviter des NPE.

Discussion :

La plupart des programmeurs écrivent d’abord la variable ou la condition et ensuite la valeur de test. En fait, c’est l’ordre correct pour les assertions. Dans certains langages, cette préférence de style est utilisée pour éviter une affectation accidentelle au lieu d’une comparaison d’égalité, ce qui peut entraîner une erreur logique dans le code.
Cela signifie que vous n’avez pas besoin de vérifier si person.getName() est null. Cela économise un peu de frappe et est sans doute plus clair une fois que vous vous y êtes habitué.


vendredi 13 octobre 2023

[ Java 21 ] Intro Virtual thread

 

Introduction

Les threads virtuels sont une nouvelle fonctionnalité introduite dans Java 21 qui permettent une exécution plus efficace des threads. 

Comprendre les threads virtuels

Avant l’introduction des threads virtuels, le nombre de threads que vous pouviez avoir dans une application Java était généralement limité par le nombre de threads du système d’exploitation. Ces threads, appelés “threads de plateforme”, sont gérés et planifiés par le système d’exploitation. Chaque thread de plateforme a une grande pile de threads et d’autres ressources qui sont maintenues par le système d’exploitation. Par conséquent, le nombre de threads de plateforme disponibles est limité par les ressources du système, y compris la CPU et la RAM.





Avec l’introduction des threads virtuels dans Java 21, vous pouvez maintenant avoir un nombre beaucoup plus grand de threads dans votre application. Les threads virtuels sont gérés et planifiés par la JVM, et non par le système d’exploitation. Ils ne sont pas liés à un thread du système d’exploitation spécifique, ce qui signifie qu’ils peuvent être répartis plus efficacement sur plusieurs processeurs. De plus, contrairement aux threads de plateforme, les threads virtuels ont généralement une pile d’appels peu profonde, ce qui signifie qu’ils utilisent moins de ressources du système.



Cela signifie que vous pouvez avoir un nombre de threads virtuels égal au nombre de tâches concurrentes dans votre application. Par exemple, si votre programme soumet 1 000 000 de tâches, il peut créer 1 000 000 de threads virtuels qui s’exécutent simultanément.


Cependant, il est important de noter que bien que les threads virtuels puissent améliorer les performances en permettant à un plus grand nombre de tâches de s’exécuter simultanément, ils ne sont pas destinés aux opérations intensives en CPU à long terme. Ils sont plus adaptés aux tâches qui passent la plupart du temps bloquées, souvent en attendant que les opérations d’E/S se terminent


Exemples pratiques

Création de threads virtuels

Voici un exemple de code qui illustre comment créer des threads virtuels en Java 211:

Runnable runnable = () -> System.out.println(Thread.currentThread().threadId());
// Créer des threads virtuels
Thread thread = Thread.ofVirtual().name("testVT").unstarted(runnable);
testVT.start();
// Créer des threads de plateforme virtuels
Thread testPT = Thread.ofPlatform().name("testPT").unstarted(runnable);
testPT.start();

Dans cet exemple, nous créons d’abord une tâche Runnable qui affiche l’ID du thread courant. Ensuite, nous utilisons les nouvelles API Thread.ofVirtual() et Thread.ofPlatform() pour créer respectivement un thread virtuel et un thread de plateforme.

Performance des threads de plateforme vs threads virtuels

Les threads virtuels peuvent aider à augmenter le débit des applications côté serveur car ces applications ont un grand nombre de tâches concurrentes, et ces tâches ont généralement un grand nombre d’attentes d’E/S. Par exemple, si votre programme soumet 1 000 000 de tâches, il peut créer 1 000 000 de threads virtuels qui s’exécutent simultanément

dimanche 1 octobre 2023

Spring AOP

Pour implémenter un aspect dans Spring, vous devez :

Activer Spring Aspect dans votre application.

Créer une classe d'aspect.

Définir une méthode d'aspect et indiquer à Spring quand et quoi intercepter.

Implémenter la logique de l'aspect dans la méthode d'aspect. 


Ces étapes vous permettront d'ajouter des fonctionnalités transversales à votre application, telles que la gestion des logs, la sécurité ou la gestion des transactions, sans avoir à modifier le code métier de votre application.


Pattern d'affichage du msg commence par Pool 
pour chercher les log de notre produit Pool 

chaque resource continet une opération 

statut en cours et fin de l'opération 
paramètres d'appel

retour du service

dimanche 17 septembre 2023

[Spring] Existe-t-il des limitations liées aux proxies dans Spring AOP?

La réponse courte : Oui, il y'a des limitations liées aux proxies dans Spring AOP 

Plus en détaille :

La nécessité d'annoter des méthodes avec @Transactional et de ne le faire que pour les méthodes publiques dans Spring découle principalement des limitations de Spring AOP et de la manière dont Spring gère la gestion des transactions.


Voici pourquoi :


Spring AOP et Proxies : Spring utilise la programmation orientée aspect (AOP) pour gérer la gestion des transactions. Lorsque vous ajoutez l'annotation @Transactional à une méthode, Spring crée un proxy autour de cette méthode. Ce proxy intercepte les appels à la méthode et gère la logique de transaction avant et après l'exécution de la méthode réelle. Cela permet à Spring de fournir une gestion déclarative des transactions sans exiger que vous codiez manuellement tout le code de gestion des transactions.


Limitation des Proxies : Les proxies créés par Spring AOP ont certaines limitations. L'une de ces limitations concerne les méthodes non publiques (telles que les méthodes privées, protégées ou package-private). Les proxies Spring ne peuvent pas intercepter les appels aux méthodes non publiques, car ils sont générés à partir de classes d'interface ou d'objets sous-jacents, et seuls les appels aux méthodes publiques peuvent être interceptés.


Visibilité des Méthodes Transactionnelles : Pour que Spring puisse intercepter les appels à une méthode annotée @Transactional, cette méthode doit être publique. Cela signifie que vous ne pouvez pas utiliser @Transactional sur des méthodes non publiques, car les transactions ne seront pas correctement gérées par Spring.

Ils peuvent également avoir un impact sur l'utilisation du cache dans une application Spring. Tout comme la gestion des transactions, le mécanisme de mise en cache de Spring AOP repose sur l'utilisation de proxies pour intercepter les appels aux méthodes et gérer la logique de mise en cache. Par conséquent, les mêmes limitations s'appliquent en ce qui concerne les méthodes que vous pouvez annoter pour utiliser le cache.


Voici comment cela fonctionne :


Mise en Cache avec Spring : Spring offre un support intégré pour la mise en cache en utilisant des annotations telles que @Cacheable, @CacheEvict, et @CachePut. Lorsqu'une méthode annotée avec @Cacheable est appelée, Spring intercepte cet appel à l'aide d'un proxy, vérifie si les résultats de la méthode sont déjà mis en cache, et si tel est le cas, renvoie la valeur mise en cache au lieu d'exécuter réellement la méthode. Cela améliore les performances en évitant des calculs coûteux pour les mêmes entrées.


Limitations de Proxies : Comme mentionné précédemment, les proxies Spring AOP ont des limitations, notamment la nécessité que les méthodes annotées soient publiques. De plus, les méthodes de mise en cache doivent être exécutées sur l'objet proxy généré par Spring pour que la logique de mise en cache fonctionne. Cela signifie que si vous avez une méthode non publique et que vous tentez d'annoter cette méthode avec @Cacheable, la mise en cache ne fonctionnera pas correctement, car Spring ne pourra pas intercepter les appels à cette méthode non publique via le proxy.


Méthodes Publiques pour la Mise en Cache : Pour que la mise en cache fonctionne correctement, vous devez annoter des méthodes publiques que vous souhaitez mettre en cache. Ces méthodes doivent être appelées à travers l'objet proxy Spring pour que la logique de mise en cache puisse être appliquée.


En résumé, tout comme pour la gestion des transactions, les méthodes que vous souhaitez mettre en cache avec Spring doivent être publiques et appelées via l'objet proxy Spring pour bénéficier de la mise en cache. Les limitations liées aux proxies s'appliquent à la mise en cache, ce qui signifie que les méthodes non publiques ne seront pas correctement gérées par le mécanisme de mise en cache de Spring.

jeudi 14 septembre 2023

[Spring] Comprendre le fonctionnement de l'attribut d'isolation des transactions

 Introduction


Lorsque plusieurs transactions d'une même application ou de différentes applications fonctionnent simultanément sur le même jeu de données, cela peut entraîner de nombreux problèmes inattendus. Pour résoudre ces problèmes, il est essentiel de spécifier comment vous souhaitez que vos transactions soient isolées les unes des autres. C'est là qu'intervient l'attribut d'isolation des transactions. Dans cet article, nous allons explorer comment cet attribut fonctionne et comment il peut résoudre les problèmes courants liés aux transactions concurrentes.

https://www.baeldung.com/spring-transactional-propagation-isolation#transactional-isolations 


Problème


Les transactions concurrentes peuvent provoquer quatre types de problèmes :


Lecture sale (Dirty read) : Dans ce scénario, deux transactions, T1 et T2, sont en cours. T1 lit un champ qui a été mis à jour par T2 mais qui n'a pas encore été validé. Si T2 est annulée ultérieurement, la valeur lue par T1 deviendra temporaire et invalide.


Lecture non répétable (Nonrepeatable read) : Ici, T1 lit un champ, puis T2 met à jour ce champ. Si T1 lit à nouveau ce champ, la valeur sera différente de ce qu'elle était précédemment.


Lecture fantôme (Phantom read) : Dans ce cas, T1 lit certaines lignes d'une table, puis T2 insère de nouvelles lignes dans cette table. Lorsque T1 lit à nouveau la même table, il y aura des lignes supplémentaires auxquelles il n'avait pas accès précédemment.


Pertes de mises à jour (Lost updates) : T1 et T2 sélectionnent tous deux une ligne à mettre à jour et, en fonction de l'état de cette ligne, effectuent une mise à jour. Ainsi, l'une écrase l'autre alors que la deuxième transaction aurait dû attendre que la première soit validée avant de procéder à sa sélection.


Solution


En théorie, les transactions devraient être complètement isolées les unes des autres (c'est-à-dire sérialisables) pour éviter tous les problèmes mentionnés ci-dessus. Cependant, ce niveau d'isolation aurait un impact considérable sur les performances, car les transactions devraient s'exécuter dans un ordre séquentiel strict. Dans la pratique, il est possible de faire fonctionner les transactions à des niveaux d'isolation inférieurs afin d'améliorer les performances.


L'attribut d'isolation des transactions permet de définir le niveau d'isolation souhaité pour une transaction donnée. Les niveaux d'isolation couramment utilisés incluent :


Isolation de lecture non répétable (Read Uncommitted) : Dans ce niveau, aucune isolation n'est garantie. Les transactions peuvent lire des données non validées par d'autres transactions en cours, ce qui peut entraîner des problèmes de lecture sale, de lecture non répétable et de pertes de mises à jour.


Isolation de lecture répétable (Read Committed) : Ce niveau permet d'éviter les lectures sales, mais les problèmes de lecture non répétable et de pertes de mises à jour peuvent toujours survenir.


Isolation de répétition de lecture (Repeatable Read) : Ce niveau résout les problèmes de lecture non répétable en garantissant que toutes les données lues par une transaction restent constantes jusqu'à la fin de cette transaction. Cependant, les lectures fantômes peuvent toujours se produire.


Isolation sérialisable (Serializable) : C'est le niveau le plus strict, garantissant une isolation totale. Cependant, cela peut avoir un impact significatif sur les performances, car les transactions doivent s'exécuter de manière séquentielle.


Conclusion


L'attribut d'isolation des transactions est un outil essentiel pour gérer les problèmes liés aux transactions concurrentes. En comprenant les différents niveaux d'isolation disponibles et en choisissant celui qui convient le mieux à vos besoins, vous pouvez garantir que vos transactions fonctionnent de manière fiable tout en optimisant les performances de votre système. Il est important de peser soigneusement les avantages et les inconvénients de chaque niveau d'isolation pour prendre la décision la plus appropriée en fonction de votre application et de vos exigences.

NB :
La gestion de l'isolation des transactions dépend de la capacité du moteur de base de données sous-jacent à prendre en charge différents niveaux d'isolation. Les applications et les frameworks n'ont généralement pas un contrôle direct sur la gestion de l'isolation des transactions, mais ils peuvent interagir avec le moteur de base de données pour spécifier le niveau d'isolation souhaité.

mardi 12 septembre 2023

[Spring boot] @SpringBootConfiguration vs @Configuration?

SpringBootConfiguration


Introduction :


L'annotation @SpringBootConfiguration est un élément essentiel de Spring Boot, un framework de développement d'applications Java. Elle indique qu'une classe fournit une configuration spécifique à une application Spring Boot. Cette annotation peut être utilisée comme une alternative à l'annotation @Configuration standard de Spring, permettant ainsi à la configuration d'être trouvée automatiquement, notamment dans le cadre de tests unitaires. Dans cet article, nous explorerons en détail l'utilisation de l'annotation @SpringBootConfiguration, ses implications dans le développement d'applications Spring Boot, et la différence entre @SpringBootConfiguration et @Configuration.


Utilisation de @SpringBootConfiguration :


Pour comprendre l'utilisation de l'annotation @SpringBootConfiguration, examinons un exemple concret. Supposons que vous développiez une application Spring Boot qui nécessite une configuration personnalisée, telle que la configuration d'une source de données ou de certaines propriétés spécifiques. Vous pouvez créer une classe de configuration annotée avec @SpringBootConfiguration pour définir cette configuration.

@SpringBootConfiguration 
public class MyAppConfig {
// définir des beans de configuration et des propriétés spécifiques à votre application.
}

Dans cet exemple, MyAppConfig est une classe de configuration spécifique à Spring Boot. Vous pouvez y définir des beans de configuration et des propriétés spécifiques à votre application.


Utilisation de @SpringBootConfiguration dans les tests unitaires :


Une utilisation courante de @SpringBootConfiguration est dans le contexte des tests unitaires. Lorsque vous écrivez des tests pour votre application Spring Boot, vous pouvez l'utiliser pour charger la configuration de l'application.


Supposons que vous ayez une classe de service que vous souhaitez tester :


@Service

public class MyService {

    // Service logic

}

Vous pouvez écrire un test unitaire pour cette classe en utilisant @SpringBootTest avec @SpringBootConfiguration :


@SpringBootTest(classes = MyAppConfig.class)

public class MyServiceTest {

    

    @Autowired

    private MyService myService;

    

    @Test

    public void testMyService() {

        // Test logic using myService

    }

}

Dans cet exemple, @SpringBootTest charge la configuration de l'application, y compris MyAppConfig, ce qui vous permet d'accéder à MyService pour effectuer des tests unitaires.


Différence avec @Configuration :


La principale différence entre @SpringBootConfiguration et @Configuration réside dans leur utilisation au sein d'applications Spring Boot. Alors que @Configuration est l'annotation standard de Spring pour déclarer une classe de configuration, @SpringBootConfiguration est spécifique à Spring Boot et offre des fonctionnalités supplémentaires liées au contexte d'application spécifique à Spring Boot.


Conclusion :


L'annotation @SpringBootConfiguration est un élément essentiel de Spring Boot, permettant de définir la configuration spécifique à une application. Elle est particulièrement utile dans le contexte des tests unitaires, où elle permet de charger la configuration de l'application pour effectuer des tests précis. En général, la plupart des développeurs préfèrent utiliser l'annotation @SpringBootApplication, qui inclut implicitement @SpringBootConfiguration, simplifiant ainsi la configuration de l'application tout en tirant parti des fonctionnalités de Spring Boot.

[Spring Boot] la propriété spring.main.web-application-type

En Spring Boot, la propriété spring.main.web-application-type est utilisée pour définir le type d'application web que vous souhaitez créer. Cette propriété peut avoir l'une des trois valeurs suivantes :


none : Cela signifie qu'aucune application web n'est activée. Cela convient aux applications Spring Boot qui ne sont pas destinées à être des applications web, par exemple, les applications de ligne de commande ou les applications de traitement de fond.


servlet : Cela indique que vous souhaitez créer une application web basée sur le Servlet. Cette option est appropriée pour la création d'applications web traditionnelles qui utilisent le conteneur de servlets Java pour gérer les requêtes HTTP. Vous pouvez utiliser des annotations comme @Controller et @RequestMapping pour créer des contrôleurs web dans ce mode.


reactive : Cela indique que vous souhaitez créer une application web réactive. Lorsque vous choisissez cette option, Spring Boot utilise le paradigme de programmation réactive pour gérer les requêtes HTTP. Cela convient aux applications qui doivent gérer un grand nombre de requêtes simultanées et qui souhaitent bénéficier de la réactivité pour une meilleure évolutivité.


Par exemple, si vous définissez spring.main.web-application-type dans votre fichier application.yml comme suit :

application.yml
spring:    
    main: 
        web-application-type: servlet

Cela signifie que vous créez une application web basée sur le Servlet.


En résumé, la propriété spring.main.web-application-type permet de définir le type d'application web que vous souhaitez créer avec Spring Boot, que ce soit une application non web, une application basée sur Servlet ou une application réactive. 

Source : https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html