mardi 19 mars 2024

ReflectionTestUtils : Un outil puissant pour les tests unitaires Spring

 

Introduction

Lorsqu’il s’agit d’écrire des tests unitaires pour des composants Spring, il est souvent préférable de le faire sans charger tout le contexte Spring. Cela permet un contrôle plus granulaire et une isolation totale des composants à tester. Une approche pour réaliser cela est d’utiliser ReflectionTestUtils de Spring, qui offre une méthode pratique pour modifier les champs privés et les méthodes privées d’une classe, y compris ceux annotés avec @Value.

Problème

Lorsque vous écrivez des tests unitaires pour des composants Spring, il est souvent recommandé d’éviter de charger l’intégralité du contexte Spring. Cela peut rendre les tests plus lents et moins précis, car ils dépendent de l’état global du contexte Spring. Prenons par exemple notre classe de service MyService avec un champ @Value :

@Service
public class MyService {

    @Value("${app.name}")
    private String appName;

    // Méthodes du service

    private String generateInternalName() {
        return "Internal_" + appName;
    }
}

Lorsque vous écrivez un test unitaire pour MyService, vous souhaitez vous concentrer uniquement sur ce service sans dépendre du contexte complet de Spring.

Solution

Pour ce faire, vous pouvez écrire votre test sans charger le contexte Spring et utiliser ReflectionTestUtils pour peupler le champ appName et appeler les méthodes privées si nécessaire. Voici un exemple :

@ExtendWith(MockitoExtension.class)
public class MyServiceTests {

    @InjectMock 
    private MyService myService;

    @Before
    public void setup() {
        ReflectionTestUtils.setField(myService, "appName", "TestAppName");
    }

    @Test
    public void testAppName() {
        String appName = myService.getAppName();
        assertEquals("TestAppName", appName);
    }

    @Test
    public void testGenerateInternalName() throws Exception {
        String internalName = invokeGenerateInternalName(myService);
        assertEquals("Internal_TestAppName", internalName);
    }

    private String invokeGenerateInternalName(MyService myService) throws Exception {
        Method generateInternalNameMethod = MyService.class.getDeclaredMethod("generateInternalName");
        generateInternalNameMethod.setAccessible(true);
        return (String) generateInternalNameMethod.invoke(myService);
    }
}

Dans cet exemple, nous avons créé une instance de MyService manuellement sans passer par Spring. Puis, dans la méthode setup(), nous utilisons ReflectionTestUtils.setField() pour définir la valeur de appName sur "TestAppName". Cela nous permet de contrôler précisément les valeurs nécessaires pour le test sans charger le contexte Spring complet.

Discussion

Cette approche présente plusieurs avantages. Tout d’abord, elle rend le test plus rapide car il n’a pas besoin de charger tout le contexte Spring. Deuxièmement, elle isole le test, le rendant indépendant de l’environnement d’exécution réel. Enfin, cela permet un contrôle plus précis sur les valeurs des champs privés et l’appel des méthodes privées, en utilisant ReflectionTestUtils pour les modifier et les appeler selon les besoins du test.

Il est crucial de souligner que cette méthode est utile non seulement pour les champs privés, mais aussi pour les méthodes privées. L’utilisation de ReflectionTestUtils permet de contourner la visibilité privée lors de l’écriture des tests unitaires, garantissant que vous pouvez tester toutes les parties importantes de votre classe, même si elles sont déclarées comme privées.

Conclusion

En conclusion, utiliser ReflectionTestUtils pour peupler les champs @Value et appeler les méthodes privées lors des tests unitaires offre un moyen efficace et précis de tester les composants Spring sans charger le contexte complet. Cela permet des tests plus rapides, isolés et contrôlés, contribuant ainsi à une suite de tests plus robuste.

lundi 18 mars 2024

[kafka] KaDeck vs kafdrop vs akhq.io

 Titre : Comparaison des outils de gestion Kafka : KaDeck, Kafdrop et akhq.io

Introduction

Apache Kafka est un système de messagerie distribué open-source largement utilisé pour le traitement de flux de données en temps réel.
Pour gérer et surveiller efficacement les clusters Kafka, plusieurs outils graphiques ont été développés, dont KaDeck, Kafdrop et akhq.io. Ces outils offrent des fonctionnalités variées pour aider les développeurs et les administrateurs à interagir avec leurs clusters Kafka.

Problème

Bien que ces outils soient utiles, il peut être difficile de choisir lequel est le mieux adapté à vos besoins spécifiques. Chaque outil a ses propres forces et faiblesses, et comprendre ces différences est essentiel pour faire un choix éclairé.

Solution

Discussion

Chaque outil a ses propres avantages. Par exemple, akhq.io offre une large gamme de fonctionnalités pour gérer différents aspects d’un cluster Kafka1. D’autre part, Kafdrop est une application légère qui nécessite très peu de configuration et offre une interface utilisateur simple pour surveiller les clusters Apache Kafka2. En ce qui concerne KaDeck, bien qu’il n’y ait pas d’informations spécifiques disponibles dans ma recherche actuelle, il est également considéré comme un outil utile pour la gestion des clusters Kafka.

Il est important de noter que le choix entre ces outils dépendra largement de vos besoins spécifiques. Par exemple, si vous avez besoin d’un outil avec une large gamme de fonctionnalités pour gérer différents aspects d’un cluster Kafka, akhq.io pourrait être un bon choix. D’autre part, si vous préférez une application légère avec une interface utilisateur simple pour surveiller votre cluster Kafka, Kafdrop pourrait être plus approprié.

En conclusion, KaDeck, Kafdrop et akhq.io sont tous des outils précieux pour la gestion des clusters Kafka. Le choix entre ces outils dépendra de vos besoins spécifiques en matière de gestion des clusters Kafka.

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