Qu'est ce que DacaCacheStorage ?

DacaCacheStorage est une libraire permettant de créer une base de données mémoire clé-valeur (aussi appelé key-value store). La clé est simplement une chaine de caractère qui doit être unique. La valeur liée à la clé est générique.

DataCacheStorage a pour particularité:
 >> il offre une grande souplesse d’utilisation.
 >> Il est très léger à mettre en place.
 >> Il permet également de gérer la durée d’expiration de vos données.
 >> Il permet de transporter les éléments mise en mémoire d’un serveur vers un autre.
 >> Il permet également de recharger les élément en mémoire après un redémarrage ou un crash de votre application.

Intégration de DataCacheStorage dans votre projet Spring Boot

+

Pour simplifier la lecture de cette article, nous nommerons la librairie DataCacheStorage « Dacas ».
Nous essayerons d’expliquer son intégration à votre projet le plus clairement possible.
Nous vous montrerons comment intégrer cette solution à votre application Spring Boot en utilisant Maven.

Prérequis

Les prérequis demandés sont:
 – une bonne connaissance du langage java.
 – une connaissance de base du Framework SpringBoot.
 – une gestion des dépendances grâce à Maven ou Gradle.

Lors de cette démonstration, nous utiliserons:
 – a minima la version 8 de Java.
 – l’IDE IntelliJ
 – Spring Boot en version 2.2.5.RELEASE
 – Maven
 – DataCacheStorage en version 1.0
Vous trouverez le code source de cette démonstration via ce lien Github ou au bas de cette page.

Pour suivre ce tutoriel, vous aurez besoin d’une application Spring Boot.

Si vous ne disposez pas d’une application, générez en une vierge de toutes autres dépendances.

Note: vous pouvez générer très simplement une application Spring Boot grâce à Spring Initializr. Pour ce faire, rendez vous sur //start.spring.io/

Votre projet aura la structure suivante.

Ajout de DataCacheStorage à votre projet

Dans cet exemple, nous utiliserons la première version de DataCacheStorage.

Pour consulter les versions disponibles, rendez vous sur la centrale Maven:

//mvnrepository.com/artifact/com.digi-lion/datacachestorage

Dans le fichier pom.xml, ajoutez la dépendance suivante:

<dependency>
          <groupId>com.digi-lion</groupId>
          <artifactId>datacachestorage</artifactId>
          <version>1.0</version>
</dependency>

Voici ce à quoi votre fichier pom.xml ressemble maintenant.

A l’aide de votre IDE ou en ligne de commande, lancez la commande Maven update.

 >> Ligne de commande:
mvn clean install -U

>> Via Eclipse:
Clic droit sur votre projet, puis Maven -> Update Snapshots.

>> Via IntelliJ:
Menu Settings -> Maven -> Import Maven projects automatically.

Vous avez désormais intégré la librairie Dacas. Notez que les librairies suivantes sont également intégrées:

-> Jackson-core (version : 2.10.3)
-> Jackson-annotations (version : 2.10.3)
-> Jackson-databind (version : 2.10.3)
-> Jackson-datatype-jsr310 (version : 2.10.3)

En effet, Dacas utilisant JSON comme format de sauvegarde de vos données, la librairie Jackson est utilisée pour celui ci.

Création de la configuration de DataCacheStorage

Pour notre exemple minimaliste, nous avons créé:
  -> un objet Car (notre POJO)
  -> une classe DemoService (notre service)
  -> une classe DataCacheStorageConfig (notre classe de configuration de DataCacheStorage)
  -> un répertoire « dataCacheStorageFile » dans le dossier resources (dossier de dépôt de notre fichier de sauvegarde)

Tout d’abord, nous allons mettre en place la configuration de Dacas.
Je vous invite à copier cette classe. Nous expliquerons au fur et à mesure chaque méthode.

package com.example.demo.configuration;

import com.digilion.dataAccessClass.enumerations.DacaStorageSupportEnum;
import com.digilion.dataAccessClass.manager.DacasManager;
import com.digilion.dataAccessClass.transaction.DacasTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Scheduled;

/**
* Configuration of DataCacheStorage<br>
* <p>
* <div>
* Injection of DataCacheStorage and Build manager. Purge and storage support
* </div>
* </p>
*
* @author Digital Lion
* @since 1.0
*/
@Configuration
public class DataCacheStorageConfig {

/**
* Definition of logger
*/
Logger logger = LoggerFactory.getLogger(DataCacheStorageConfig.class);

/**
* Default constructor
*/
public DataCacheStorageConfig() {
}

@Value("${dacas.file.path}")
private String jsonFilePath;

@Value("${dacas.max.object.in.memory}")
private int limit;

@Value("${dacas.max.time.to.live}")
private Long ttl;

/**
* Run Dacas manager
*/
@Bean
public DacasManager runDacas() {
DacasManager dacasManager = new DacasManager(DacaStorageSupportEnum.FILE, limit, jsonFilePath, ttl);
logger.info("Initializing Dacas.");
return dacasManager;
}

/**
* Schedule task for automatically purge expired elements on memory
*/
@Scheduled(initialDelay = 1000*30, fixedDelay = 1000*60*30)
private void purge() {
DacasTransaction.purge();
}

/**
* Schedule task for automatically copying elements from memory to backup file
*/
@Scheduled(initialDelay = 1000*60, fixedDelay = 1000*60*60)
private void autoPush(){
DacasTransaction.push();
}
}

Détaillons maintenant cette classe DataCacheStorageConfig.

Cette classe est annotée @Configuration, elle peut être démarrée à l’aide de l’analyse des composants (Injection de dépendance).

Définissons un logger qui nous servira plus tard. Libre à vous d’utiliser votre logger préféré, dans cette exemple nous avons utilisé slf4j.

Passons aux choses plus intéressantes:

Nous avons besoin de définir 3 propriétés:
  -> le chemin du notre fichier backup (jsonFilePath).
  -> le nombre maximal d’élément que nous pourrons injecter dans Dacas (limit).
  -> la date d’expiration (en seconde) par défaut au cas où un élément injecté dans Dacas n’a pas une date d’expiration clairement définie (ttl) .

Il va donc falloir définir les trois clés suivantes dans notre fichier de propriété. Nous y reviendrons.

Déclarons maintenant un Bean grâce au décorateur @Bean. C’est ce que l’on fait avec la méthode runDacas().

Le constructeur DacasManager prend 3 paramètres :
  –> DacaStorageSupportEnum.
             —> FILE (stockage en mémoire et sauvegarde de la mémoire sur fichier)
             —> MEMORY (stockage uniquement en mémoire)
  –> limit (défini plus haut)
  –>
jsonFilePath (défini plus haut)
  –> ttl (défini plus haut)

Les deux dernières méthodes de la classe (non obligatoire mais fortement recommandée) permettent de:
 –> purger automatiquement les éléments dont la date d’expiration est dépassée.
 –> créer une sauvegarde des données présentes en mémoire vers un fichier de sauvegarde.

Ces deux tâches planifiées sont annotées @Scheduled. Dans notre exemple, ces tâches seront exécutées chaque demi-heure pour la purge et chaque heure pour la sauvegarde.

A vous de configurer la fréquence de déclenchement de ces tâches selon les besoins de votre applications.

Sachez qu’à chaque sauvegarde, Dacas supprime au préalable les données expirées .

Pour terminer avec la configuration, vous devez ajouter dans votre fichier .properties  (dans notre exemple : application.properties) les trois clés suivantes:

dacas.file.path=/home/.../demo-dacas/src/main/resources/dataCacheStorageFile/
dacas.max.object.in.memory=1000000
dacas.max.time.to.live=86400

La clé « dacas.file.path » définit le chemin d’accès au fichier backup (le fichier aura le nom: « dacasmemoriesvalues.dacas »).
La clé « dacas.max.object.in.memory » définit le nombre maximum d’élément admis en memoire.
La clé « dacas.max.time.to.live » définit la durée de vie maximale par défaut d’un élément en mémoire. Cette valeur par défaut est appliquée uniquement aux éléments empaquetés dans un DacasElement*, injectés dans Dacas, et sans date d’expiration définie. La valeur est en seconde.

*Nous expliquerons ce qu’est un DacasElement dans le paragraphe suivant.

Mise en mémoire d'éléments

DacasElement permet d’empaqueter votre objet à injecter en mémoire. Chacun de vos éléments aura, grâce à DacasElement une date d’expiration clairement définie. Si vous ne définissez pas celui ci, la date par défaut définie dans la configuration de Dacas lui sera attribuée.

Vous pouvez cependant injecter un objet directement dans Dacas, mais ceci est une mauvaise pratique car vous ne lui définissez pas de date d’expiration.

Une date d’expiration lui sera toutefois définie. Cette date sera celle renseignée dans la configuration de Dacas (ttl). L’élément sera rechargé par l’application à son redémarrage tant que la date d’expiration de l’élément ne sera pas dépassée. La seule manière « propre » de supprimer ces éléments avant leurs dates d’expiration est d’utiliser la méthode cleanup(). Toutefois, tout le cache sera vidé sans distinction, élément expiré ou non.
Cependant, si vous avez configuré Dacas en mode mémoire uniquement (DacaStorageSupportEnum.MEMORY), votre élément restera en mémoire tant que votre application sera démarrée.

Pour toutes manipulations de vos données dans Dacas, utilisez simplement DacasTransaction.
DacasTransaction vous donne la possibilité d’ajouter, lire, purger, vider, sauvegarder vos données. Vous avez seulement besoin de DacasTransaction pour toutes vos opérations.

Par exemple, créons 3 exemples de méthodes permettant d’injecter un élément dans Dacas:
– Premier exemple: exampleInjectionObjectDataIntoDacas() sans utiliser DacasElement. (Mauvaise pratique)
– Deuxième exemple: exampleInjectionDacasElementIntoDacas() en utilisant DacasElement. (Bonne pratique d’injection d’éléments dans Dacas)
– Dernier exemple: exampleInjectionDacasElementIntoDacasWithBuilder() en utilisant DacasElementBuilder. (Bonne pratique d’injection d’éléments dans Dacas)

package com.example.demo.services;

import com.digilion.dataAccessClass.element.DacasElement;
import com.digilion.dataAccessClass.transaction.DacasTransaction;
import com.example.demo.bo.Car;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

/**
* Example of service class
*
* @author Digital Lion
* @since 1.0
*/
@Service
public class DemoService {

/**
* Default Constructor
*/
public DemoService() {
}

/**
* Example of injecting Object into DataCacheStorage.
* <p>
* <u>Inserting Objects in Dacas without packaging them into a DacasElement is not recommended</u>
* </p>
* @param carToInject
*/
public void exampleInjectionObjectDataIntoDacas(Car carToInject) {
// Committing into DacaStorage
// Since you didn't define DateTimeStorage and/or ExpiredDateTime, the object will be converted to DacasElement with the default expiring date defined in configuration.
DacasTransaction.commit(carToInject.getLicensePlate(), carToInject);
}

/**
* Example of injecting Object packaged into DacasElement to DataCacheStorage.
* @param carToInject
*/
public void exampleInjectionDacasElementIntoDacas(Car carToInject) {
DacasElement dacasElement = new DacasElement();
dacasElement.setValue(carToInject);
// It is recommended to fix DateTimeStorage and ExpiredDateTime for your DacasElement.
// If you don't define DateTimeStorage and/or ExpiredDateTime, default values defined on configuraton are set.
LocalDateTime now = LocalDateTime.now();
dacasElement.setDateTimeStorage(now);
dacasElement.setExpiredDateTime(now.plusHours(2));

// Committing your object packaged into DacasElement in DacasStrorage
DacasTransaction.commit(carToInject.getLicensePlate(), dacasElement);
}

/**
* Example to inject Object packaged into DacasElement (using Builder) to DataCacheStorage.
* @param carToInject
*/
public void exampleInjectionDacasElementIntoDacasWithBuilder(Car carToInject) {
// You can also use Builder
DacasElement dacasElementBuilder;
LocalDateTime now = LocalDateTime.now();
dacasElementBuilder = new DacasElement.DacasElementBuilder<>().value(carToInject).dateTimeStorage(now).lifeTime(now.plusHours(2)).build();

// Committing your object packaged into DacasElement in DacasStrorage
DacasTransaction.commit(carToInject.getLicensePlate(), dacasElementBuilder);
}

/**
* Example of accessing your data
* @param key
* @return car
*/
public Car exampleAccessToYourData(String key) {
// When you retrieve your value, you directly retrieve your value and not a DacasElement.
// Cast your retrieved data! And that's all.
return (Car) DacasTransaction.get(key);
}

/**
* Example of pushing element in memory into the dacasmemoriesvalues.dacas file
*/
public void forceBackup() {
// If you configure storage support as a file,
// method push() send all data from memory to file referenced into properties file.
// Before writing, all expired data will be flushed.
DacasTransaction.push();

}

}

DacasManager.dacasStrorage vous donne accès à certaines méthodes utilitaires afin de manipuler vos éléments injectés dans Dacas.

Récupération d'éléments

Depuis notre classe principale, nous allons simplement créé trois objets myBeetle, myFiat et myFord de type Car.
Nous allons injecté ces trois éléments dans Dacas.

La clé que nous allons choisir pour nos éléments doit bien entendu être unique et de type String. Nous choisirons pour notre exemple, la plaque d’immatriculation de nos véhicules.

Ensuite, nous allons forcé l’écriture de nos informations en mémoire vers le fichier de sauvegarde. Cette étape n’est pas nécessaire, nous forcerons cette écriture lors de cette exemple afin de décrire le contenu du fichier de sauvegarde créé. Dacas se chargera lui même de la sauvegarde grâce à la tache planifiée décrite plus haut de cet article.

Enfin, nous récupérerons les données grâce à leur clé unique.
L’objet récupéré de Dacas étant de type Object, vous devez procéder à un cast (ici nous casterons en type Car).

package com.example.demo;

import com.example.demo.bo.Car;
import com.example.demo.services.DemoService;
import com.example.demo.services.PerformanceReducedTest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.time.LocalDate;
import java.time.Month;

@SpringBootApplication
public class DemoApplication {

/**
* Definition of logger
*/
private static Logger logger = LoggerFactory.getLogger(DemoApplication.class);

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
runDataCacheStorageDemo();
}

/**
* Demonstration method
*/
public static void runDataCacheStorageDemo() {
DemoService demoService = new DemoService();
// Example of object to inject into DataCacheStorage
Car myBeetle = new Car("VolksWagen", "Beetle", 50, LocalDate.of(1972, Month.JANUARY, 12), "MB-123-RC");
Car myFiat= new Car("Fiat", "500", 38, LocalDate.of(1982, Month.AUGUST, 25), "TR-987-KV");
Car myFord= new Car("Ford", "GT", 550, LocalDate.of(1991, Month.NOVEMBER, 03), "GT-987-LM");

// Example of direct injection of Object into DataCacheStorage.
// Possible but not recommended because you don't set an expiration date at your data
demoService.exampleInjectionDacasElementIntoDacas(myBeetle);

// Example of good practice for the injection of Object into DataCacheStorage.
// Your object is packaged into DacasElement.
demoService.exampleInjectionObjectDataIntoDacas(myFiat);
demoService.exampleInjectionDacasElementIntoDacasWithBuilder(myFord);

// We are forcing the backup before scheduled task execution (for this example of application)
demoService.forceBackup();

// Example of accessing your data
Car myBeetleRetrieved = demoService.exampleAccessToYourData("MB-123-RC");
logger.info("First Circulation date of my retrieved car : " + myBeetleRetrieved.getFistCirculationDate());
}
}

Si vous avez des éléments qui demande une inscription immédiate dans le fichier de sauvegarde après leur injections dans Dacas; privilégiez la  méthode commitAndPush(). Celle-ci inscrit immédiatement les éléments en mémoire sur le fichier de sauvegarde. Votre fichier de sauvegarde sera donc à jour.

Fichier de sauvegarde généré

Si vous choisissez une sauvegarde de type File (DacaStorageSupportEnum.FILE), vos éléments seront à la fois conservés en mémoire mais également sauvegardés sous forme JSON dans un fichier nommé : dacasmemoriesvalues.dacas . Nous avons précédemment configuré le chemin d’accès à celui ci dans les propriétés de Spring (application.properties).

Voici le contenu de ce fichier pour notre exemple:

Redémarrage ou crash de l'application

Lors de la mise en place de la configuration de DacasManager, Dacas vérifie si un fichier dacasmemoriesvalues.dacas existe dans le répertoire renseigné dans les propriétés de votre applications.

Si ce fichier n’existe pas, DacasManager va en générer un nouveau. Assurez vous d’avoir les permissions de votre serveur pour écrire dans ce répertoire.

Si le fichier existe, DacasManager parcourt le fichier, les éléments dont la date d’expiration sont dépassées, seront supprimés du fichier. Tous les autres éléments sont rechargés en mémoire.

Si votre application est tombée pour une raison ou une autre, à son redémarrage, DacasManager remettra en mémoire les éléments présents dans le fichier de sauvegarde. Veillez donc à configurer correctement la fréquence de sauvegarde de vos données.

Vous pouvez également selon le contexte de votre application, mutualiser ou transférer d’un serveur à un autre vos données mémoires grâce au fichier de sauvegarde Dacas.

Quelques chiffres

Temps d’injection d’éléments dans DataCacheStorage.

Temps de lecture des valeurs présentes dans DataCacheStorage.
Nous pouvons garantir que vous aurez toujours accès à vos données en moins d’une milliseconde.

En bleu, le temps pour injecter des éléments dans Dacas, puis effectuer une sauvegarde une fois l’injection terminée.
En rouge, le temps pour injecter et sauvegarder des éléments un par un, puis effectuer la prochaine injection d’élément.

Pour des applications ayant des quantités de données très importantes à ajouter en mémoire de façon très récurrente (injection supérieure à 10 000 éléments par seconde), il est très fortement déconseillé de procéder à une sauvegarde après chaque injection de vos données en mémoire. Privilégiez la sauvegarde par lot ( DacasTransaction.commitMultiAndPush( ) ) ou par intervalle de temps.

Ainsi DataCacheStorage est près de 400 fois plus rapide en optimisant les intervalles entre chaque sauvegarde.
Cette courbe montre l’importance d’une bonne configuration des tâches automatisées de DataCacheStorage (sauvegarde et purge) pour optimiser les performances de Dacas et améliorer les performances de votre application.

Temps nécessaire à DataCacheStorage pour parcourir l’ensemble des éléments afin de purger ceux expirés et créer une sauvegarde à jours.

Les précédents tests ont été effectués sur sept machines de configurations différentes. Les valeurs sont une moyenne des sept résultats des tests.

Liens utiles

Accéder au code source de ce tutoriel sur GitHub :
//github.com/MiladArdehali/dataCacheStorageExemple

Java Documentation:
//digi-lion.com/soft/datacachestorage-java-doc/

Merci d’avoir suivi ce tutoriel. J’espère avoir été le plus clair possible. N’hésitez pas à me faire part de vos ressentis, points d’amélioration ou de blocage grâce  au formulaire de contact, en cliquant ici.

A très bientôt, amusez vous bien!