Pages

vendredi 15 mai 2015

Créer une application Rest avec Spring Data Rest

Dans le cadre d'un projet personnel, j'avais besoin de disposer rapidement d'une application qui expose des services Rest à une application cliente écrite en Angular JS. Au début je voulais utiliser Firebase, mais je voulais pouvoir lancer sans problème mon application en offline et disposer si nécessaire d'un peu plus de fonctionnalités.

J'ai donc choisi d'écrire une application Spring Boot et d'utiliser le plugin Spring Data Rest. Ce plugin scrupte tous vos objets qui étendent l'interface CrudRepository et les expose automatiquement via une API REST HATEOAS.

HATEOAS, pour ceux qui ne connaissent pas, impose quelques règles lorsque vous faites une application REST. Le sigle signifie " Hypermedia as the Engine of Application State". Le principe est de fournir tous les élements dans l'API pour que le client puisse interagir avec vos données. Pour résumer votre API envoie en plus des données, des éléments qui permettent de naviguer vers les différentes données de l'application.

Le mieux pour comprendre c'est de prendre un exemple. Nous allons donc voir comment écrire une application qui permet d'exposer une liste de talks et les speakers associés en quelques lignes. Tout le code montré dans cet article est disponible sous Github.


Générer un projet Spring Boot

Si vous disposez de la dernière version de IntelliJ vous pouvez le faire via les wizards. Sinon rendez vous sur la page https://start.spring.io/. Indiquez les données permettant d'identifier votre projet, puis sélectionnez les modules qui vont nous intéresser ici
  • Rest respositories (va importer Spring Data Rest et vous n'avez pas non plus à sélectionner HATEOAS)
  • JPA 
  • Base de données H2 afin de stocker nos données dans une base en mémoire



Configuration du projet

Vous pouvez maintenant ouvrir le projet dans votre IDE préféré. Le descripteur du projet (maven dans mon exemple) doit vous montrer les dépendances

        <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-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

Vous devez également disposer d'une classe qui permet de lancer votre projet

@EnableJpaRepositories
@SpringBootApplication
public class AngularAndTestsApplication {

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

Testez que votre application fonctionne en lançant la commande
mvn spring-booot run

Elle est disponible  à l'adresse http://localhost:8080
Pour le moment votre application n'expose qu'un service qui expose le numéro de version

Création de vos entités

Nous allons créer un objet Talk qui peut contenir une liste de speakers (je passe les détails des objets entités qui n'apportent rien comme certaines propriétés, les getters/setters et autres méthodes utilitaires)

@Entity
public class Talk {
    @GeneratedValue
    @Id
    private Long id;
    private String title;
    @Column(columnDefinition = "TEXT")
    private String description;
    @OneToMany(targetEntity=Speaker.class, mappedBy="talk")
    private List<Speaker> speakers = new ArrayList<>();
    
    ...
}

Puis un objet Speaker qui contiendra une relation ManyToOne vers un Talk

@Entity
public class Speaker {
    @GeneratedValue
    @Id
    private Long id;
    private String firstname;
    private String lastname;
    @ManyToOne
    private Talk talk;

    ...
}

Configuration du projet

Par défaut les services REST n'exposent pas les identifiants dans le JSON envoyé au client. Pour pouvoir les ajouter vous pouvez rajouter une classe de configuration héritant de la classe SpringBootRepositoryRestMvcConfiguration et dans laquelle nous allons déclarer les beans pour lesquels nous souhaitons exposer les id.

@Configuration
public class AngularAndTestsConfig extends SpringBootRepositoryRestMvcConfiguration{

    @Override
    protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(Speaker.class);
        config.exposeIdsFor(Talk.class);
    }
}


Pour en finir avec la partie déclarative, je souhaite que mon application ait un contexte pour pouvoir par exemple la charger via l'URL http://localhost:8080/api plutôt que http://localhost:8080. Pour celà vous pouvez ajouter un fichier de propriétés application.properties qui contiendra
server.contextPath=/api

Définition de nos repository

Nous arrivons bientôt au terme de notre projet. Nous définissons deux répositories qui ne sont que des interfaces. Spring s'occupe de générer les classes concrètes en fonction des noms des méthodes. Il suffit de respecter la convention exposée ici.

public interface SpeakerRepository extends CrudRepository<Speaker, Long> {
    Collection<Speaker> findByFirstname(@Param("name") String firstname);
}

public interface TalkRepository extends CrudRepository<Talk, Long> {
}

Test de l'application

Nous pouvons vérifier que notre application fonctionne en écrivant un test unitaire

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = AngularAndTestsApplication.class)
public class SpeakerRepositoryTest {

    @Autowired
    private SpeakerRepository repository;

    @Test
    public void should_find_speaker_by_firstname() throws Exception {
        repository.save(new Speaker().setFirstname("Dan").setLastname("North"));
        assertThat(repository.findByFirstname("Dan").iterator().next().getLastname()).isEqualTo("North");
    }
}

Nous pouvons également écrire une classe qui initialise une donnée dans la base

@Component
public class Initializer {

    @Autowired
    private SpeakerRepository speakerRepository;

    @Autowired
    private TalkRepository talkRepository;

    @PostConstruct
    public void init() {

        speakerRepository.save(
                new Speaker()
                        .setFirstname("Guillaume")
                        .setLastname("EHRET")
                        .setTalk(talkRepository.save(
                                new Talk()
                                  .setTitle("Un exemple avec Spring Data Rest")
                        )));
    }
}

Si la syntaxe de création d'une donnée vous interpelle je vous conseille de lire mon article sur l'écriture des DTO en Java.

Vous pouvez lancer l'application via l'URL http://localhost:8080/api.
Vous pouvez naviguer directement vers vos données



Conclusion

Personnellement j'ai vraiment été séduit par la rapidité à laquelle j'ai pu créer une application Restfull. Au départ, j'ai eu un peu de mal pour manipuler l'API REST mais tout ceci est lié à ma méconnaissance des bonnes pratiques à ce sujet. Lire la documentation HATEOAS m'a donné plusieurs élements de réponse.

Au niveau des objets nous ne définissons plus des objets imbriqués les uns dans les autres mais nous utilisons des aLink. Par exemple si je veux sauvegarder un speaker j'enverrai un JSON de la forme suivante
Pour une création j'envoie cet objet via une méthode POST à l'URL /speakers
Pour une modification je l'envoie via une méthode PUT à l'URL /speakers/1 (ne pas oublier l'ID)

Voila je vous laisse découvrir par vous même Spring Data Rest et les sources de ma démo sont disponibles sous Github.


Aucun commentaire:

Enregistrer un commentaire

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