Quick POC : Spring boot + EmberJS

Quick POC : Spring boot + EmberJS

Pour découvrir le framework EmberJS plus en détail, on va se faire un petit annuaire en lecture seule de programmeurs célèbres à base de Spring boot pour le back et d’EmberJS pour le front.

EmberJS peut-être téléchargé ici : https://emberjs.com/

Si vous voulez savoir ce que fait ce framework, vous pouvez consulter cet article.

Pour télécharger directement le code source de ce projet : https://github.com/alexandrelanglais/spring-boot-emberjs

Mise en place du back

C’est parti, on va commencer par créer un projet Maven vide dans Eclipse et récupérer le POM d’une application spring-boot sur le Getting started de Spring

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>fr.demandeatonton</groupId>
    <artifactId>spring-boot-emberjs-backend</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

On change simplement le groupId et l’artifactId.

On va ensuite créer la classe contenant le point d’entrée de l’application spring-boot :

package fr.demandeatonton;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
  public static void main(String[] args) throws Exception {
    SpringApplication.run(Application.class, args);
  }
}

Et enfin spécifier dans le POM quelle est la classe à démarrer avec le plugin maven spring-boot

<properties>
    <java.version>1.8</java.version>
    <start-class>fr.demandeatonton.Application</start-class>
</properties>

On peut maintenant lancer l’application avec la commande mvn spring-boot:run ou via Eclipse :

On peut ensuite naviguer sur http://localhost:8080 pour constater que tout fonctionne bien

On a une belle erreur : c’est normal, on a encore rien codé. Mais notre spring-boot tourne correctement.


Programmation du back

On va se créer un repository ainsi qu’un controller qui va nous servir de base pour notre annuaire.

Pour commencer, créons le bean représentant les données que l’on va récupérer :

package fr.demandeatonton.model.beans;

public class Programmer {
  private int id;
  private String name;
  private String description;

  public Programmer(int id, String name, String description) {
    this.id = id;
    this.name = name;
    this.description = description;
  }

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

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

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  @Override
  public String toString() {
    return "Programmer [id=" + id + ", name=" + name + ", description=" + description + "]";
  }

}

Ensuite, un repository en mémoire, bien suffisant pour le scope de cet article :

package fr.demandeatonton.model.repositories;

import java.util.ArrayList;
import java.util.List;

import fr.demandeatonton.model.beans.Programmer;

@Repository
public class MemoryRepository {
  private static List<Programmer> programmers = new ArrayList<>(3);

  static {
    programmers.add(new Programmer(1, "Linus Torvalds", "Créateur de Linux"));
    programmers.add(new Programmer(2, "Dennis Ritchie", "Créateur du C"));
    programmers.add(new Programmer(3, "Ken Thompson", "Créateur d'Unix et du B"));
  }

  public Programmer findById(int id) {
    for (Programmer c : programmers) {
      if (c.getId() == id) {
        return c;
      }
    }
    return null;
  }
}

Et enfin un controlleur REST qui va nous fournir nos données :

package fr.demandeatonton.controllers;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import fr.demandeatonton.exceptions.ObjectNotFoundException;
import fr.demandeatonton.model.beans.Programmer;
import fr.demandeatonton.model.repositories.MemoryRepository;

@RestController
@RequestMapping("/programmers")
public class CustomerController {
  private final MemoryRepository repository;

  @Autowired
  public CustomerController(MemoryRepository repository) {
    this.repository = repository;
  }

  @RequestMapping(value = "/{id}", method = RequestMethod.GET)
  public Programmer getProgrammer(@PathVariable("id") Integer id) {
    Programmer programmer = repository.findById(id);
    if (programmer == null) {
      throw new ObjectNotFoundException("Programmeur inconnu - id: " + id);
    }
    return programmer;
  }

}

Notez que l’on vérifie que le programmeur est bien trouvé, sinon on renvoie une exception de type ObjectNotFoundException :

package fr.demandeatonton.exceptions;

public class ObjectNotFoundException extends RuntimeException {

  public ObjectNotFoundException(String message) {
    super(message);
  }

}

Pour spécifier à Spring comment gérer les exceptions, on va utiliser un @ControllerAdvice :

package fr.demandeatonton.exceptions;

import java.util.logging.Level;
import java.util.logging.Logger;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

@ControllerAdvice
public class ControllerExceptionHandler {
  private static final Logger LOG = Logger.getLogger(ControllerExceptionHandler.class.getName());

  @ResponseStatus(HttpStatus.NOT_FOUND)
  @ExceptionHandler(value = { ObjectNotFoundException.class })
  public void entityNotFoundException(Exception e) {
    LOG.log(Level.SEVERE, e.getMessage(), e);
  }
}

On relance spring-boot et on teste en se rendant sur http://localhost:8080/programmers/1

Parfait ! On a notre back prêt à accueillir notre front.


Création de l’application front

Nous allons utiliser le generator d’EmberJS pour générer les différents modules du front. Commençons avec l’application en elle-même. Placez-vous dans le dossier qui contient le projet maven et tapez cette commande dans une invite windows : ember new spring-boot-emberjs-frontendde manière à avoir l’arborscence suivante dans eclipse :

Démarrons le serveur ember pour vérifier que l’appli est OK :

cd spring-boot-emberjs-frontend
ember serve

On navigue sur la page http://localhost:4200

Nous sommes maintenant prêts à programmer notre application EmberJS pour que celle-ci nous affiche notre annuaire.


Programmation de l’appli front

On va commencer par créer un modèle et une route pour récupérer les données de notre controleur spring et avoir une page web qui nous les affiche.

Toujours dans le répertoire spring-boot-emberjs-frontend, on va exécuter les commandes suivantes :

ember generate model programmer
ember generate route programmers/show

Cette commande nous a généré plusieurs fichiers d’intérêt :

  • app\models\programmer.js qui contiendra la définition d’un programmeur
  • app\routes\programmers\show.js qui contiendra les traitements à effectuer avec les données avant de rendre la vue
  • app\templates\programmers\show.hbs qui constitue la visualisation d’un programmeur

Je laisse de côté les fichiers de tests pour le scope de cet article.

Attention : EmberJS n’apprécie guère le mélange d’espaces et de tabulations dans les fichiers sources. Si vous avez des soucis, consultez la sortie console à la recherche d’erreurs potentielles.

Modifions le fichier app\models\programmer.js pour l’adapter à notre modèle de données :

import DS from 'ember-data';

export default DS.Model.extend({
  name: DS.attr(),
  description: DS.attr()
});

Le generator a ajouté la route programmers/show dans le fichier racine router.js. Modifions le afin de spécifier que la route show prend un id dans son URL :

import Ember from 'ember';
import config from './config/environment';

const Router = Ember.Router.extend({
  location: config.locationType,
  rootURL: config.rootURL
});

Router.map(function() {
  this.route('programmers', function() {
    this.route('show', { path: '/show/:id' });
  });
});

export default Router;

Adaptons maintenant le fichier show.js afin de lui indiquer ou récupérer ses données :

import Ember from 'ember';

export default Ember.Route.extend({
  model(params) {
    return this.get('store').findRecord('programmer', params.id);
  }
});

Et enfin, affichons les données récupérées dans la vue show.hbs :

<h2>id: {{model.id}}</h2>
<h2>name: {{model.name}}</h2>
<h2>description: {{model.description}}</h2>

On teste en allant sur http://localhost:4200/programmers/show/1

Et BIM ! 404 sur le GET /programmers/1 . C’est normal, puisque EmberJS essaye de se connecter à son propre serveur (localhost:4200) alors que notre REST tourne sur localhost:8080

EmberJS exécute la requête Ajax en fonction de ce qui a été ajouté dans le fichier app/route/programmers/show.js, en l’occurrence this.get(‘store’).findRecord(‘programmer’, params.id_programmer); EmberJS ajoutera automatiquement un ‘s’ à ‘programmer’ pour requérir le service REST

Configuration d’un adaptateur REST

On va ajouter un adapter à EmberJS pour lui signifier qu’il doit effectuer ses requêtes sur le port 8080. Créons un fichier nommé app/adapters/application.js :

import DS from 'ember-data';

export default DS.RESTAdapter.extend({
   host: 'http://localhost:8080'
});

Et pour ne pas avoir d’erreur de CrossDomain CORS, configurons le back spring pour qu’il autorise les requêtes provenant d’EmberJS, c’est à dire de l’url localhost:4200. Dans le fichier Application.java :

package fr.demandeatonton;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@SpringBootApplication
public class Application {
  public static void main(String[] args) throws Exception {
    SpringApplication.run(Application.class, args);
  }

  @Bean
  public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurerAdapter() {
      @Override
      public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOrigins("http://localhost:4200");
      }
    };
  }
}

On redémarre spring-boot et on teste :

Allons bon, maintenant on peut accéder au service, mais EmberJS n’arrive pas à utiliser les données JSON comme nous l’entendions.

C’est normal car selon la documentation d’EmberJS, on doit ajouter a la racine du JSON le nom de l’élément :

{
  "person": {
    "firstName": "Jeff",
    "lastName": "Atwood"
  }
}

Or, notre service REST nous renvoie ceci actuellement :

{"id":1,"name":"Linus Torvalds","description":"Créateur de Linux"}

Adaptation du JSON

Corrigeons cela dans spring. Ajoutons cette annotation dans le fichier Programmer.java :

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;

@JsonTypeInfo(include = As.WRAPPER_OBJECT, use = Id.NAME)
public class Programmer {
  private int id;
  private String name;
  private String description;

Maintenant en allant sur http://localhost:8080/programmers/1 on obtient bien des données JSON au format attendu :

{"Programmer":{"id":1,"name":"Linus Torvalds","description":"Créateur de Linux"}}

Testons de nouveau l’application http://localhost:4200/programmers/show/1 :

Une dernière chose à régler : supprimer la page d’accueil. Pour cela, on modifie le fichier app/templates/application.hbs pour ne plus laisser que la ligne {{outlet}} comme ceci :

{{outlet}}

On rafraichit la page :

Et voilà ! Nous avons fait communiquer notre front EmberJS avec notre back Spring !

Merci Tonton

Retrouvez les sources de ce projet sur https://github.com/alexandrelanglais/spring-boot-emberjs

Sources :

https://emberjs.com/

https://spring.io/guides/gs/spring-boot/

https://spring.io/guides/gs/rest-service-cors/

 

One thought on “Quick POC : Spring boot + EmberJS

Laisser un commentaire