Découvrir Kotlin en migrant une webapp Spring Boot

Lors la dernière conférence Google I/O qui s’est tenue en mai 2017, Google a officialisé le support de Kotlin sur Android. Google n’est pas le seul acteur de l’IT à miser sur ce nouveau langage créé par JetBrains (l’éditeur de l’IDE IntelliJ) et s’exécutant sur la JVM (mais pas que). En effet, dès février 2016, Pivotal proposait de développer des applications Spring Boot avec Kotlin. En janvier 2017, ils annonçaient que la version 5 du framework Spring proposerait des fonctionnalités exclusives à Kotlin. Chez Gradle, le langage Kotlin est désormais privilégié au détriment de Groovy.

Pour découvrir ce nouveau venu dans la galaxie des langages de programmation, je me suis intéressé à migrer vers Kotlin l’application démo Spring Petclinic développée en Java et Spring Boot. Je souhaitais ici partager son code source : spring-petclinic-kotlin et énumérer les différences notables avec sa version Java.

Une migration en souplesse

En m’appuyant sur le manuel de référence de Kotlin, j’ai pu migrer l’application sans trop de difficulté et en quelques heures. IntelliJ m’a grandement facilité la tâche puisqu’un copier/coller d’une classe Java dans un fichier Kotlin (extension .kt) lançait le plugin de conversion automatique. Quelques adaptations manuelles restaient néanmoins nécessaires.

Grâce à l’interopérabilité de Kotlin avec Java, j’ai pu faire cohabiter classes Kotlin et classes Java dans le même projet IntelliJ. Au cours de la migration, cela m’a permis de vérifier régulièrement le bon fonctionnement l’application.

Des conventions qui changent

Kotlin changent certaines conventions du langage Java :

1. Les classes et les méthodes sont par défaut finales et ne peuvent être héritées / redéfinies sans l’utilisation du mot clé open
Dans Petclinic, la classe BaseEntity parente de toutes les entités JPA est déclarée ainsi :

L’omission du paramètre open déclenche une erreur de compilation des classes filles : « This type is final, so it cannot be inherited from ».

Ce changement de comportement impacte le fonctionnement de certaines librairies tierces. En effet, lors de l’utilisation d’annotations tels que @Cacheable ou @Configuration, le framework Spring utilise l’héritage pour instrumenter le code. La configuration du plugin Spring pour le compilateur Kotlin dans le pom.xml permet de s’affranchir de l’ajout du mot clé open sur les beans Spring de type @Component.

2. La visibilité des méthodes et des classes est par défaut publique
Appartenant au package visit, la classe Visit est référencée par la classe Pet du package de même niveau owner :

3. Les types primitifs de Java disparaissent. Plus besoin de choisir entre un int et un Integer : vous utiliserez un Int.

4. Le type des variables et de retour de méthode n’est plus déclaré à gauche mais à droite.
Extrait de l’interface OwnerRepository :

fun findById(@Param("id") id: Int): Owner

Il faut s’y faire et retrouver ses habitudes du bon vieux Turbo Pascal.

5. Par défaut, aucune variable ne peut être null. Le compilateur vous rappellera à l’ordre. Lorsqu’une variable peut prendre la valeur null, il est nécessaire de le préciser explicitement en faisant suivre son type par le caractère ?

6. Les getter/setter (mutateurs) des propriétés d’une classe sont générés automatiquement par Kotlin. Dans le code, on accède directement à une propriété sans passer par les mutateurs. Kotlin ajoute automatiquement l’appel au mutateur correspondant.
Là ou en Java on passait par un setter :

en Kotlin, on affecte directement la valeur à la propriété :

Bien entendu, Kotlin offre la possibilité de ne générer qu’un des 2 mutateurs et/ou de redéfinir leur implémentation. Par exemple, dans la BaseEntity.kt, la propriété isNew est évaluée à partie de l’ID de l’entité :

Une syntaxe allégée

Par rapport à Java, Kotlin se veut apporter de la concision sans perdre en lisibilité, et ceci par le biais de léger changements syntaxiques.

1. Le signe point-virgule ; en fin d’instruction devient facultatif. Et lorsqu’une méthode ne comporte qu’une seule instruction, l’utilisation d’accolades et du mot clé return ne sont plus nécessaires.
Extrait du PetController Java :

Extrait du PetController Kotlin :

2. Concernant l’héritage et l’implémentation d’une interface, les mots clés extends et implements sont remplacés par le symbole :

Code Java :

Code Kotlin :

3. Le compilateur Kotlin sait inférer le type des variables. Lorsque vous déclarez une variable en lui affectant une valeur (autre que null), il n’est plus nécessaire de spécifier son type.
Exemple issu de Owner.kt :

4. L’instruction for each permettant d’itérer sur les éléments d’une collection change de syntaxe. Kotlin passe du : au in. A noter que le type de variable n’est plus exigé.

Version Java :

Version Kotlin :

 

Des améliorations intéressantes

La plus-value de Kotlin par rapport à Java dépasse les conventions et les changements syntaxiques évoqués dans les 2 paragraphes précédents.

1. Kotlin propose de créer automatiquement des POJO avec getters, setters, méthodes equals(), hashCode(), toString() et copy() (cette dernière étant propre à Kotlin) via un mécanisme appelé data class.
La classe Vets profite de cette simplification :

2. Dans les contrôleurs Spring MVC écrits en Java, il est courant d’avoir une suite de conditions if else dont chaque bloc renvoie sur une page différente.
Extrait de la méthode processFindForm de la classe Java OwnerController:

Pour réduire le nombre de return, Kotlin permet d’utiliser le if comme expression et non plus comme instruction. Lorsqu’une branche contient plusieurs instructions, la dernière est assignée au if ; dans l’exemple ci-dessous, c’est le nom de la page :

Autant dire que Kotlin sait faire plaisir à SonarQube en limitant l’usage de l’instruction return.

Une autre façon d’écrire ce code consiste à utiliser l’expression when qui est une sorte de super switch case. Dans la classe OwnerController Kotlin, les if / else disparaissent au profit de lambdas :

3. Compatible avec Java 6, Kotlin avait introduit les lambda avant Java 8. Les collections ont été enrichies de méthodes permettant d’itérer, de filtrer, de trier, trouver un élément, récupérer le dernier … On peut y accéder directement, à savoir passer par un stream.

Extrait de la classe Owner codée en Java 6 :

Pendant en Kotlin :

La méthode find() permet de rechercher un élément dans une collection. A noter l’utilisation de l’opérateur ?: qui permet de lever une exception si find renvoie null.
Extrait de la classe PetTypeFormatter.kt :

4. Bien que par défaut les variables ne puissent être null, nous avons vu qu’il était possible de les rendre nullable. L’opérateur elvis ?. permet d’accéder à des propriétés sans craindre des NullPointerException :

Kotlin proposent d’autres fonctionnalités fortes intéressantes que je n’ai pas eu l’occasion de mettre en œuvre dans Spring Petclinic. Je pense notamment aux extension functions qui permettent d’ajouter dynamiquement des méthodes à une classe.

Des changements plus discutables

1. La déclaration de constantes ne passe plus par l’usage des mots clés static final devant la propriété d’une classe. A la place, Kotlin propose de passer par des constantes de portée globale ou par des objets companion.

Constantes globales (extrait de PetControllerTest.kt) :

Constantes internes à une classe (extrait de PetValidator.kt) :

2. Un développeur Spring et JPA utilise massivement les annotations. Or, lorsque la propriété est multi-valuée (tableau), Kotlin requière l’utilisation du mot clé arrayOf
Exemple d’un mapping @OneToMany JPA extrait de Owner.kt :

Pour le coup, on perd en lisibilité par rapport à Java. Heureusement, ce désagrément devrait être corrigé dans une prochaine version de Kotlin : KT-11235

Conclusion

Pour un développeur Java, l’apprentissage de Kotlin se fera sans trop d’effort.
Sans révolutionner Java, Kotlin permet de moderniser sa syntaxe. Il apporte quelques nouveautés fortes appréciables une fois qu’on y a goûté.

Débutant sur Kotlin, je suis preneur de toute suggestion d’amélioration (et il doit y en avoir !!). Tout contributeur est le bienvenu.

Pour aller jusqu’au bout de l’exercice, il serait intéressant de migrer le build Maven vers un build Gradle écrit en Kotlin (réf. #2). Là encore, avis aux amateurs.

Ressources :

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *