Pages

jeudi 18 février 2016

Gestion de configuration et Spring

Aujourd'hui je voulais vous parler de gestion de configuration dans les applications Java/Spring et vous faire partager des petits trucs que j'ai appris dernièrement et qui au final peuvent vous simplifier la vie comme elles me l'ont simplifié.


Propriétés en Java

Dans une application Java nous avons tous utilisé des fichiers properties. Par exemple application.properties

devmind.name=Dev-Mind
devmind.twitter=DevMind_FR
 
Pour lire le contenu du fichier, vous pouvez passer par la classe java.util.Properties

public class DevMindTest {
  Properties properties = new Properties();
  {
    try {
      properties.load(getClass().getResourceAsStream("/application.properties"));
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }

  @Test public void contextLoads() {
    Assertions.assertThat(
      properties.getProperty("devmind.name")).isEqualTo("Dev-Mind");
  }
}

D'autres manières de passer des informations à un programme

En Java il existe également d'autres manières de passer des informations à un programme. Vous pouvez récupérer des valeurs définies dans des variables d'environnement

Assertions.assertThat(System.getenv("JAVA_HOME")).endsWith("/java/jdk1.8.0_40");

Mais aussi passer des arguments à votre programme.

public class SimpleMainApplication {

    public static void main(String[] args) {
        for (String arg : args) {
            System.out.println("Devmind app argument " + arg);
        }
    }
}

La commande
java com.devmind.properties.SimpleMainApplication monarg1 monarg2

donnera
Devmind app argument monarg1
Devmind app argument monarg2

Il existe d'autres sources de données. Par exemple pour des ressources comme des bases de données ou autre nous pouvons définir des sources JNDI…

Spring et les fichiers properties

Avec Spring vous pouvez injecter directement des propriétés dans votre code via l'annotation @Value.

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



Bien évidemment il faut que le fichier de ressources soit chargé. Mais si vous utilisez Spring Boot en respectant la convention (fichier nommé application.properties placé dans les ressources de votre application), vous pouvez importer directement des propriétés sans avoir à vous soucier de savoir comment les données sont chargées. Si ce n'est pas le cas vous pouvez spécifier les fichiers dont vous avez besoin dans un bean de configuration via l'annotation @PropertySource .

@Configuration@PropertySource("classpath:/monfichier.properties")
public class DevMindConf {
}

Spring apporte un niveau d'abstraction assez intéressant car la valeur que vous pouvez utiliser peut être une variable d'environnement, un argument de programme, une valeur définie dans un fichier….

Bien évidemment si une propriété est définie à plusieurs endroits, il existe un ordre de prise en compte et vous pouvez surcharger certaines propriétés. Ce mécanisme est très intéressant quand vous voulez personnaliser votre application sur un environnement. Dans une applicaton Spring Boot voici les possibilités

  • Arguments passés à l'application 
  • Propriétés définies en JSON et stockées dans une variable d'environnement ou propriété système nommée SPRING_APPLICATION_JSON 
  • Attributs JNDI
  • Propriétés systèmes JAVA 
  • Variable d'environnement de l'OS 
  • Fichiers de properties spécifiques à un environnement (voir un peu plus bas) définis en dehors d'un jar 
  • Fichiers de properties spécifiques à un environnement (voir un peu plus bas) packagés dans le jar de l'application 
  • Fichiers de properties définis en dehors d'un jar 
  • Fichiers de properties packagés dans le jar de l'application 
  • Fichiers définis via l'annotation @PropertySource dans vos beans de configuration
  • Les propriétés par défaut définies via SpringApplication.setDefaultProperties 
Les éléments les plus hauts de la liste surchargent ceux qui sont en dessous. Définir des variables d'environnement, permet par exemple de surcharger ce que vous avez défini dans un fichier par défaut intégrer dans un jar.

Le format YAML

Si vous en avez marre d'utiliser le format .properties et sa duplication de clé, vous pouvez opter pour le format YAML. Sa structure arborescente est à mon sens plus lisible. La classe SpringApplication gère ce format dès que vous avez la librairie SnakeYAML dans votre classpath. Avec SpringBoot c'est le cas et vous pouvez donc utiliser un fichier application.yml en lieu et place du traditionnel application.properties

devmind:
  name: Dev-Mind
  twitter: DevMind_FR
spring:
  datasource:
    driver-class-name: ${DATABASE_DRIVER:com.mysql.jdbc.Driver}
    username: ${DATABASE_USERNAME:devmind}
    password: ${DATABASE_PASSWORD:devmind}

Dans l'exemple ci dessus j'ai surchargé les propriétés Spring Boot pour définir la base de données. J'ai défini plusieurs valeurs par défaut que je peux surcharger par exemple par des variables d'environnement. Je peux changer par exemple le mot de passe par défaut en définissant une variable d'environnement DATABASE_PASSWORD.


Utiliser plusieurs propriétés dans vos services

Si vous avez besoin d'utiliser plusieurs propriétés et si vous injectez ces propriétés via @Value, votre composant peut vite devenir assez illisible. Vous pouvez donc directement injecter le bean de type Environment et utiliser les méthodes getProperty ou getRequiredProperty.

@Autowired
private Environment environment;

public String getDevMindAdress() {
    return String.format("Society %s in city %s",
            environment.getRequiredProperty("devmind.name"),
            environment.getProperty("devmind.city", "Saint Etienne"));
}

Au delà de la lisibilité cet artifice va également simplifier vos tests. Si vous utilisez par exemple Mockito vous pourrez facilement redéfinir les différentes valeurs des propriétés dans vos tests

public class DevMindServiceTest {
  @Rule
  public MockitoRule rule = MockitoJUnit.rule();
  @Mock
  private Environment environment;
  @InjectMocks
  private DevMindService devMindService;

  @Test
  public void getDevMindAdress() {
    Mockito.when(environment.getRequiredProperty("devmind.name"))
      .thenReturn("Dev-Mind");
    Mockito.when(environment.getProperty("devmind.city", "Saint Etienne"))
      .thenReturn("Lyon");
    Assertions.assertThat(devMindService.getDevMindAdress())
      .isEqualTo("Society Dev-Mind in city Lyon");
  }
}


Une autre manière de faire est de regrouper vos paramètres dans des beans en utilisant l'annotation @ConfigurationProperties. Vous pourrez ensuite injecter ces beans à l'endroit où vous en avez besoin

@Component@ConfigurationProperties(prefix="devmind")
public class DevMindConfiguration {
    private String name;
    private String twitter;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getTwitter() {
        return twitter;
    }

    public void setTwitter(String twitter) {
        this.twitter = twitter;
    }
}


Avec Spring Boot le binding entre vos fichiers de configuration et ces beans est automatique. Pour une application Spring standard vous devrez ajouter l'annotation @EnableConfigurationProperties sur un de vos beans de configuration.

Cette solution est aussi intéressante quand vous utilisez les mêmes propriétés à plusieurs endroits dans votre code

Les profils

Dans une application Spring, vous pouvez utiliser les profils quand vous voulez spécifier des comportements différents en fonction des environnements. Vous définissez le ou les profils actifs au démarrage de votre application soit via des arguments ou une variable d'environnement nommée spring.profiles.active.

Au niveau des beans de configuration vous pouvez passer par l'annotation @Profile pour ne charger que certains beans dans des environnements donnés

@Configuration
@Profile("production")
public class DevMindConf {
}

Au niveau de vos fichiers properties vous pouvez utiliser le nom du profile dans le nom des fichiers pour ne charger que ceux nécessaires.
application-{profile}.properties.

Comme nous l'avons vu un peu plus haut, les données définies dans ces fichiers surchargent celles définies dans le fichier application.properties qui contiendra tous vos paramètres par défaut et communs à tous les contextes.

Si vous faites du YAML vous pouvez personnaliser certaines valeurs liées à un profil directement dans le fichier application.yml en ajoutant des sections délimitées par ---.

devmind:
  name: Dev-Mind
---
spring:
  profiles: default
devmind:
  city: Saint Etienne
---
spring:
  profiles: cloud
  server:
    port: 80

Je vais m'arrêter la dessus pour cette fois et j'espère que ces petites astuces vous aideront dans vos projets.

Aucun commentaire:

Enregistrer un commentaire

Remarque : Seul un membre de ce blog est autorisé à enregistrer un commentaire.