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 !