Spring Data REST est une partie du projet Spring Data qui facilite la création de web services RESTful. Il permet plus précisément d’exposer des ressources REST via le protocole HTTP tout en respectant le principe HATEOAS exigeant que le client REST se déplace uniquement dans l’application web en suivant des URIs au format hypermédia.
Cet article a pour vocation de présenter les étapes utiles à la création d’une application Spring Boot permettant d’accéder à une base de données NoSQL MongoDB avec Spring Data REST. Cette application est volontairement non minimale, dans le but de faire intervenir plusieurs notions intéressantes parmi lesquelles se trouvent notamment l’interface MongoOperations ainsi que les classes Query et Criteria. Le code complet de l’application est disponible sur GitHub.
Configuration du projet Spring Boot
Afin de configurer le contexte Spring de notre application, nous utiliserons l’outil de construction de projets Maven. En plus des dépendances concernant Spring Boot déclarées dans le fichier pom.xml nous devrons déclarer celles concernant Spring Data REST ainsi que Spring Data MongoDB, comme suit :
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> </dependencies>
Découpage du projet Spring Data Rest en packages
Ayant décidé d’utiliser une architecture à 5 couches notamment dans le but de bien structurer le code, le projet a été divisé en plusieurs packages :
- com.invivoo.webapp : contient le point d’entrée de notre application
- com.invivoo.webapp.domain : contient les objets du domaine et les interfaces des repositories permettant de les manipuler
- com.invivoo.webapp.infrastructure : contient les implémentations des classes relatives au système de stockage choisi permettant la récupération des objets du domaine
- com.invivoo.webapp.application : contient les classes relatives au fonctionnement de l’application et qui ne font pas partie du domaine (sécurité, configuration, …)
- com.invivoo.webapp.interfaces : contient les classes décrivant la couche d’API REST
Implémentation des classes Developer et Project
Dans cette application sera considéré un développeur possédant un prénom, un nom, ayant un certain âge et travaillant sur un projet caractérisé par un nom et une date de création. De plus, chaque développeur possèdera un identifiant unique généré automatiquement par Spring. L’implémentation des classes Developer et Project est la suivante :
package com.invivoo.webapp.domain; import org.springframework.data.annotation.Id; public class Developer { @Id private String id; private String firstName; private String lastName; private int age; private Project project; //getters and setters } package com.invivoo.webapp.domain; public class Project { private String name; private String creationDate; //getters and setters }
Création de l’interface DeveloperRepository
Nous allons à présent créer une interface étendant MongoRepository<Developer, String> et y déclarer les méthodes basiques de recherche dont nous pourrions avoir besoin pour filtrer des données selon certains critères. Ces critères seront quant à eux spécifiés à l’aide de noms d’attributs couplés à des mots-clés parmi ceux présents dans la documentation officielle de Spring Data MongoDB. Cette syntaxe particulière permettra à Spring Data REST de créer automatiquement une implémentation de notre API à l’exécution. Par ailleurs, dans le cadre de notre projet, l’implémentation de l’interface DeveloperRepository est la suivante :
package com.invivoo.webapp.infrastructure; //some imports public interface DeveloperRepository extends MongoRepository<Developer, String> { List<Developer> findByProjectName(@Param("projectName") String projectName); List<Developer> findByAge(@Param("age") int age); }
Le endpoint vers lequel la ressource va être exportée correspond par défaut au nom de la classe concernée écrit en minuscules et mis au pluriel, à savoir developers dans notre cas. Cependant, avec l’annotation optionnelle @RepositoryRestResource il est possible de modifier ce endpoint ainsi que le nom correspondant dans la base de données en procédant comme suit :
@RepositoryRestResource(collectionResourceRel = "people", path = "people")
L’annotation @Param permet de spécifier le nom d’un paramètre que l’on pourra passer dans l’URI de la méthode concernée. Ainsi les paramètres projectName et age présents dans notre interface pourront être utilisés de la manière suivante :
/findByProjectName/?projectName=lumos /findByAge/?age=30
Implémentation de la classe DeveloperController
Nous avons précédemment expliqué que Spring Data REST fournissait l’implémentation de méthodes basiques de recherche dont le prototype respectait une certaine syntaxe. Cependant, pour les méthodes plus complexes l’implémentation reste de notre ressort, comme dans le cas présent où nous souhaitons consulter la liste de tous les développeurs d’un âge donné travaillant sur le même projet. Ainsi nous avons créé la classe DeveloperController annotée @RestController et permettant par conséquent de traiter des requêtes CRUD ainsi que de renvoyer directement la réponse JSON à l’utilisateur, sans passer par une vue :
package com.invivoo.webapp.interfaces; //some imports @RestController public class DeveloperController { @Autowired private DeveloperService developerService; @RequestMapping(path = "/findByAgeAndProjectName", method = RequestMethod.GET) public List<Developer> findByAgeAndProjectName( @RequestParam("age") int age, @RequestParam("projectName") String projectName) { return developerService.findByAgeAndProjectName(age, projectName); } }
L’annotation @Autowired est utilisée pour faire de l’injection automatique de dépendances basée sur le type DeveloperService et l’annotation @RequestMapping permet quant à elle de faire le lien entre l’URI /findByAgeAndProjectName invoquée via GET et la méthode findByAgeAndProjectName().
Enfin, l’annotation @RequestParam permet de spécifier le nom d’un paramètre que l’on pourra passer dans l’URI de la méthode concernée. Ainsi les paramètres projectName et age présents dans notre méthode pourront être utilisés de la manière suivante :
/findByAgeAndProjectName/?age=30&projectName=lumos
Implémentation de la classe DeveloperServiceImpl
Commençons tout d’abord par créer l’interface DeveloperService contenant uniquement des prototypes de méthodes :
package com.invivoo.webapp.domain; //some imports public interface DeveloperService { List<Developer> findByAgeAndProjectName(int age, String projectName); }
Nous allons à présent créer la classe DeveloperServiceImpl contenant l’implémentation de la méthode complexe findByAgeAndProjectName() :
package com.invivoo.webapp.infrastructure; //some imports @Service public class DeveloperServiceImpl implements DeveloperService { @Autowired private MongoOperations mongoOperations; @Override public List<Developer> findByAgeAndProjectName(int age, String projectName) { List<Developer> developers = new ArrayList<>(); Query searchQuery = new Query(); searchQuery.addCriteria(Criteria.where("age").is(age).and("project.name").is(projectName)); developers = mongoOperations.find(searchQuery, Developer.class); return developers; } }
L’annotation @Service permet d’indiquer que la classe DeveloperServiceImpl sera gérée par l’IOC de Spring et que nous pourrons l’utiliser dans notre application via le mécanisme d’injection en utilisant @Autowired. Ici, l’annotation @Autowired permet quant à elle d’injecter automatiquement les dépendances basées sur l’interface MongoOperations implémentée par MongoTemplate et spécifiant un ensemble d’actions. Par ailleurs, afin d’implémenter notre méthode nous avons fait appel à l’action find() dans le but de récupérer la liste de tous les développeurs dont l’âge et le nom du projet sont les mêmes que ceux spécifiés dans les critères de la requête.
Implémentation de la classe TestController
Dans le but de tester simplement notre contrôleur a été créée la classe TestController implémentée ci-dessous et contenant une méthode :
- créant un développeur possédant un prénom, un nom et ayant un certain âge,
- créant un projet caractérisé par un nom et une date de création,
- attribuant ce projet au développeur,
- retournant le développeur.
package com.invivoo.webapp.interfaces; //some imports @RestController public class TestController { @RequestMapping(path = "/test", method = RequestMethod.GET) public Developer test() { final Developer developer = new Developer(); developer.setFirstName("firstname_toto"); developer.setLastName("lastname_toto"); developer.setAge(26); final Project project = new Project(); project.setName("project_toto"); project.setCreationDate("2020-03-20"); developer.setProject(project); return developer; } }
L’annotation @RequestMapping permet de faire le lien entre l’URI /test invoquée via GET et la méthode test().
Implémentation de la classe Application
Avant de lancer notre application, il nous reste à créer la classe contenant le point de départ de notre programme :
package com.invivoo.webapp; //some imports @SpringBootApplication @EnableMongoRepositories public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
L’annotation @SpringBootApplication permet d’ajouter les annotations suivantes au projet :
- @Configuration : qui indique que cette classe est une classe de configuration Spring
- @EnableAutoConfiguration : qui indique à Spring de configurer par défaut les différents modules en se basant sur les dépendances, les beans présents et les propriétés
- @ComponentScan : qui permet de définir le périmètre à partir duquel le framework va scanner les classes pour identifier toutes celles qu’il va devoir instrumenter. Ici, comme l’annotation ne comporte aucune indication, ce sera à partir du package webapp, celui de la classe de configuration.
L’annotation @EnableMongoRepositories permet quant à elle d’activer les Repositories MongoDB.
Avant de lancer son application
Une fois le développement de son application terminé et si ce n’est pas déjà fait, il est à présent nécessaire d’installer une base de données MongoDB sur sa machine en local !
Pour ce faire, que vous soyez sous Linux, Windows ou macOS je vous invite à suivre les étapes décrites sur le manuel d’installation de MongoDB.
Ensuite vous pourrez lancer MongoDB en ligne de commande sur un terminal puis lancer votre application.
Tester son application Spring Data REST
Pour tester votre application, vous pouvez par exemple utiliser le client REST curl permettant d’accéder en lecture à la base de données mais également d’en modifier le contenu à l’aide des opérations suivantes :
- POST : pour l’ajout d’un développeur
- PUT : pour le remplacement intégral d’un développeur
- PATCH : pour le remplacement de certaines caractéristiques d’un développeur
- DELETE : pour la suppression d’un développeur
Créons notre premier développeur en utilisant la requête suivante :
curl -i -X POST -H "Content-Type:application/json" -d "{ \"firstName\" : \"Peppa\", \"lastName\" : \"Pig\", \"age\" : 4, \"project\" : {\"name\" : \"play\", \"creationDate\" : \"2004-05-31\"} }" http://localhost:8080/developers
Nous obtenons la réponse :
HTTP/1.1 201 Location: http://localhost:8080/developers/5e73b8a1842d7917049c1085 Content-Type: application/hal+json;charset=UTF-8 Transfer-Encoding: chunked Date: Thu, 19 Mar 2020 18:23:29 GMT { "firstName" : "Peppa", "lastName" : "Pig", "age" : 16, "project" : { "name" : "play", "creationDate" : "2004-05-31" }, "_links" : { "self" : { "href" : "http://localhost:8080/developers/5e73b8a1842d7917049c1085" }, "developer" : { "href" : "http://localhost:8080/developers/5e73b8a1842d7917049c1085" } } }
Remplaçons ce développeur par un autre :
curl -X PUT -H "Content-Type:application/json" -d "{ \"firstName\" : \"Harry\", \"lastName\" : \"Potter\", \"age\" : 29, \"project\" : {\"name\" : \"lumos\", \"creationDate\" : \"1997-06-26\"} }" http://localhost:8080/developers/5e73b8a1842d7917049c1085
Modifions l’âge de ce développeur :
curl -X PATCH -H "Content-Type:application/json" -d "{ \"firstName\" : \"Harry\", \"lastName\" : \"Potter\", \"age\" : 30, \"project\" : {\"name\" : \"lumos\", \"creationDate\" : \"1997-06-26\"} }" http://localhost:8080/developers/5e73b8a1842d7917049c1085
Pour accéder au répertoire de développeurs vous pouvez utiliser la commande :
curl http://localhost:8080/developers
Pour ne voir que les données correspondant au développeur créé :
curl http://localhost:8080/developers/5e73b8a1842d7917049c1085
Pour connaître toutes les requêtes tapez la commande :
curl http://localhost:8080/developers/search
Enfin, supprimons ce développeur :
curl –X DELETE http://localhost:8080/developers/5e73b8a1842d7917049c1085
Félicitations ! Vous avez à présent terminé ce tutoriel expliquant comment accéder à MongoDB depuis votre application Spring Boot avec Spring data REST et avez à présent toutes les cartes en main pour développer votre propre application.
Intéressé par Spring Boot ? Retrouvez l’ensemble de nos articles sur ce framework !