BT
x Votre opinion compte ! Merci de bien vouloir répondre au sondage InfoQ concernant vos habitudes de lecture !

Créer un Web Service RESTful avec Spring Boot pour accéder à des données dans un cluster Aerospike

Écrit par Peter Milne , traduit par Philippe Charrière le 15 juil. 2014 |

Spring Boot est un très bon moyen pour débuter avec Spring. Il vous permet de construire des applications Spring avec peu d'effort.

Aerospike est une base de données distribuée et répliquée en mémoire, optimisée pour utiliser à la fois la mémoire DRAM et Flash/SSD native.

Aerospike a aussi une grande fiabilité et est compatible ACID. Les développeurs peuvent rapidement faire évoluer leur cluster de base de données de deux nœuds à vingt noeuds sans arrêter le service de base de données.

Qu'est-ce que vous allez construire

Cet article va vous guider à travers la création d'un service Web RESTful simple avec Spring Boot.

Vous allez construire un service qui accepte une demande HTTP GET. Il répond avec le JSON suivant :

{"expiration":121023390,"bins":{"DISTANCE":2446,"DEST_CITY_NAME":"New York","DEST":"JFK","YEAR":2012,"ORI_AIRPORT_ID":"14679","DEP_TIME":"802","DAY_OF_MONTH":12,"DEST_STATE_ABR":"NY","ORIGIN":"SAN","FL_NUM":160,"CARRIER":"AA","ORI_STATE_ABR":"CA","FL_DATE":"2012/01/12","AIR_TIME":291,"ORI_CITY_NAME":"San Diego","ELAPSED_TIME":321,"ARR_TIME":"1623","AIRLINE_ID":19805},"generation":1}

Les données que vous allez utiliser sont des informations de vols commerciaux (inclus dans le code d'exemple), c'est un fichier de données flights_from.csv. Il contient environ un million de dossiers de vol.

Il y a également de nombreuses fonctionnalités ajoutées à votre application "out-of-the-box" pour la gestion du service en environnement de production (ou autre). Cela vient fonctionnellement de Spring (voir le guide de Spring : Construire un service web RESTful).

Ce dont vous aurez besoin

Mettre en place le projet

Vous pouvez utiliser n'importe quel système de build pour la création d'applications avec Spring, mais le code Maven est présenté dans cet article. Si vous n'êtes pas familier avec Maven, vous pouvez vous référer au guide de Spring : Construire des Projets Java avec Maven.

Vous aurez aussi besoin de construire et d'installer le client Java Aerospike dans votre dépôt local. Téléchargez et décompressez la source, puis exécutez les commandes Maven suivantes :

mvn install:install-file -Dfile=client/depends/gnu-crypto.jar -DgroupId=org.gnu -DartifactId=gnu-crypto -Dversion=2.0.1 -Dpackaging=jar
mvn clean
mvn package

Création de la structure de répertoire

Dans le projet de votre choix, créez la structure suivante :

└─ src/
   └─ main/
      └─ java/
         └─ com/
            └─ aerospike/
               └─ client/
                  └─ rest/

Création du pom Maven

Créez un fichier pom.xml pour Maven à la racine de votre projet, avec le contenu suivant :

<?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>com.aerospike</groupId>
      <artifactId>aerospike-restful-example</artifactId>
      <version>1.0.0</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>0.5.0.M4</version>
    </parent>
      <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
            <!-- Aerospike client. -->
        <dependency>
              <groupId>com.aerospike</groupId>
              <artifactId>aerospike-client</artifactId>
              <version>3.0.9</version>
        </dependency>
        <!-- Apache command line parser. -->
        <dependency>
              <groupId>commons-cli</groupId>
              <artifactId>commons-cli</artifactId>
              <version>1.2</version>
        </dependency>
  </dependencies>

  <properties>
      <start-class>com.aerospike.client.rest.AerospikeRESTfulService</start-class>
  </properties>

  <build>
      <plugins>
          <plugin> 
              <artifactId>maven-compiler-plugin</artifactId> 
              <version>2.3.2</version> 
          </plugin>
          <plugin>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
      </plugins>
  </build>
  <repositories>
      <repository>
          <id>spring-snapshots</id>
          <name>Spring Snapshots</name>
          <url>http://repo.spring.io/libs-snapshot</url>
          <snapshots>
              <enabled>true</enabled>
          </snapshots>
      </repository>
  </repositories>
  <pluginRepositories> 
      <pluginRepository>
          <id>spring-snapshots</id>
          <name>Spring Snapshots</name>
          <url>http://repo.spring.io/libs-snapshot</url>
          <snapshots>
              <enabled>true</enabled>
          </snapshots>
       </pluginRepository>
  </pluginRepositories>

</project>

Cela paraît effrayant, mais çà ne l'est pas réellement.

Création de la classe de traduction en JSON

L'API AeroSpike retournera un objet Record et il contiendra la génération, l'expiration et les valeurs des nombres "bin" (séries de valeurs numériques). Mais vous voulez avoir ces valeurs renvoyées au format JSON. La meilleure façon d'y parvenir est d'utiliser une classe de traduction.

Créer une classe de traducteur avec le code suivant. Il s'agit d'une classe générique qui traduira un objet Record AeroSpike en un JsonObject.

src/main/java/com/aerospike/client/rest/JSONRecord.java
package com.aerospike.client.rest;
import java.util.Map;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.aerospike.client.Record;
/**
* JSONRecord is used to convert an Aerospike Record
* returned from the cluster to JSON format
*
*/
@SuppressWarnings("serial")
public class JSONRecord extends JSONObject {
      @SuppressWarnings("unchecked")
      public JSONRecord(Record record){
            put("generation", record.generation);
            put("expiration", record.expiration);
            put("bins", new JSONObject(record.bins));
            if (record.duplicates != null){
                  JSONArray duplicates = new JSONArray();
                  for (Map<String, Object> duplicate : record.duplicates){
                        duplicates.add(new JSONObject(duplicate));
                  }
                  put("duplicates", duplicates);
             }
       }
}

Cette classe n'est pas compliquée et est très générique. Vous pouvez spécialiser votre traduction de JSON pour les objets Record spécifiques.

Créer un contrôleur de ressource

Avec Spring, les points de terminaison de services REST sont des contrôleurs Spring MVC. Le code suivant gère une requête GET pour /as/{namespace}/{set}/getAll/1234 et retourne l'enregistrement (objet Record) dont la clé est 1234, où {namespace} est la variable de chemin d'accès (path) pour l'espace de noms AeroSpike et {set} est la variable de chemin d'accès (path) pour le set AeroSpike.

src/main/java/com/aerospike/client/rest/RESTController.java
package com.aerospike.client.rest;
import java.io.BufferedReader;
import java.io.InputStreamReader;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import com.aerospike.client.AerospikeClient;
import com.aerospike.client.Bin;
import com.aerospike.client.Key;
import com.aerospike.client.Record;
import com.aerospike.client.policy.Policy;
import com.aerospike.client.policy.WritePolicy;

@Controller
public class RESTController {
      @Autowired
      AerospikeClient client;
    @RequestMapping(value="/as/{namespace}/{set}/getAll/{key}", method=RequestMethod.GET)
    public @ResponseBody JSONRecord getAll(@PathVariable("namespace") String namespace, 
            @PathVariable("set") String set,
            @PathVariable("key") String keyvalue) throws Exception {
    Policy policy = new Policy();
    Key key = new Key(namespace, set, keyvalue);
     Record result = client.get(policy, key);
     return new JSONRecord(result);
  }
}

Le contrôleur de service REST fournira les données dans le corps de la réponse (response body), dans notre cas un objet JSON qui représente l'enregistrement lut à partir d'AeroSpike.

L'annotation @ResponseBody explique à Spring MVC d'écrire l'objet renvoyé dans le corps de la réponse.

Créer une classe exécutable principale (méthode main)

Mettez en oeuvre la méthode main pour créer un contrôleur Spring MVC. La meilleure façon de le faire est d'utiliser la "helper" classe SpringApplication.

src/main/java/com/aerospike/client/rest/AerospikeRESTfulService.java
package com.aerospike.client.rest;
import java.util.Properties;
import javax.servlet.MultipartConfigElement;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import com.aerospike.client.AerospikeClient;
import com.aerospike.client.AerospikeException;
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class AerospikeRESTfulService {
      @Bean
      public AerospikeClient asClient() throws AerospikeException {
            Properties as = System.getProperties();
            return new AerospikeClient(as.getProperty("seedHost"), 
                  Integer.parseInt(as.getProperty("port")));
      }
      @Bean
      public MultipartConfigElement multipartConfigElement() {
            return new MultipartConfigElement("");
      }
      public static void main(String[] args) throws ParseException {
             Options options = new Options();
             options.addOption("h", "host", true, 
                   "Server hostname (default: localhost)");
             options.addOption("p", "port", true, "Server port (default: 3000)");
             // parse the command line args
             CommandLineParser parser = new PosixParser();
             CommandLine cl = parser.parse(options, args, false);
             // set properties
             Properties as = System.getProperties();
             String host = cl.getOptionValue("h", "localhost");
             as.put("seedHost", host);
             String portString = cl.getOptionValue("p", "3000");
             as.put("port", portString);
             // start app
             SpringApplication.run(AerospikeRESTfulService.class, args);
      }
}

L'annotation @EnableAutoConfiguration a été ajoutée : elle permet un chargement de valeurs par défaut (comme le conteneur de servlet intégré) en fonction du contenu de votre classpath, et d'autres choses.

Il est également annoté avec @ComponentScan, qui "dit" à Spring de scanner l'ensemble du package bonjour pour les contrôleurs (avec toutes les autres classes de composants annotées).

Enfin, cette classe est annotée avec @Configuration. Cela vous permet de configurer une instance de la classe AerospikeClient comme un bean Spring.

Il y a aussi un bean MultipartConfigElement qui est défini. Cela vous permet de traiter les opérations POST avec ce service.

La plupart du corps de la méthode main lit simplement les arguments de ligne de commande et renseigne les propriétés du système pour spécifier l'adresse de l'hôte (seed host) et le port du cluster Aerospike.

Trop facile !

Charger les données

Vous allez vouloir charger des données vers ce service. Pour cela vous devrez ajouter une nouvelle méthode au contrôleur REST pour traiter le fichier téléchargé (uploaded). Dans cet exemple, ce sera un fichier CSV contenant la liste des vols.

src/main/java/com/aerospike/client/rest/RESTController.java
@Controller
public class RESTController {
   . . . (code omitted) . . .
   /*
    * CSV flights file upload
    */
   @RequestMapping(value="/uploadFlights", method=RequestMethod.GET)
   public @ResponseBody String provideUploadInfo() {
       return "You can upload a file by posting to this same URL.";
   }
   @RequestMapping(value="/uploadFlights", method=RequestMethod.POST)
   public @ResponseBody String handleFileUpload(@RequestParam("name") String name, 
          @RequestParam("file") MultipartFile file){
     if (!file.isEmpty()) {
           try {
                 WritePolicy wp = new WritePolicy();
                 String line = "";
                 BufferedReader br = new BufferedReader(new InputStreamReader(file.getInputStream()));
                 while ((line = br.readLine()) != null) {
                     // use comma as separator
                     String[] flight = line.split(",");

                     /*
                      * write the record to Aerospike
                      * NOTE: Bin names must not exceed 14 characters
                      */
                          client.put(wp,
                                new Key("test", "flights",flight[0].trim() ),
                                new Bin("YEAR", Integer.parseInt(flight[1].trim())),
                                new Bin("DAY_OF_MONTH", Integer.parseInt(flight[2].trim())),
                                new Bin("FL_DATE", flight[3].trim()),
                                new Bin("AIRLINE_ID", Integer.parseInt(flight[4].trim())),
                                new Bin("CARRIER", flight[5].trim()),
                                new Bin("FL_NUM", Integer.parseInt(flight[6].trim())),
                                new Bin("ORI_AIRPORT_ID", Integer.parseInt(flight[7].trim())),
                                new Bin("ORIGIN", flight[8].trim()),
                                new Bin("ORI_CITY_NAME", flight[9].trim()),
                                new Bin("ORI_STATE_ABR", flight[10].trim()),
                                new Bin("DEST", flight[11].trim()),
                                new Bin("DEST_CITY_NAME", flight[12].trim()),
                                new Bin("DEST_STATE_ABR", flight[13].trim()),
                                new Bin("DEP_TIME", Integer.parseInt(flight[14].trim())),
                                new Bin("ARR_TIME", Integer.parseInt(flight[15].trim())),
                                new Bin("ELAPSED_TIME", Integer.parseInt(flight[16].trim())),
                                new Bin("AIR_TIME", Integer.parseInt(flight[17].trim())),
                                new Bin("DISTANCE", Integer.parseInt(flight[18].trim()))
                          );
                          System.out.println("Flight [ID= " + flight[0] 
                                                  + " , year=" + flight[1] 
                                                  + " , DAY_OF_MONTH=" + flight[2] 
                                                  + " , FL_DATE=" + flight[3] 
                                                  + " , AIRLINE_ID=" + flight[4] 
                                                  + " , CARRIER=" + flight[5] 
                                                  + " , FL_NUM=" + flight[6] 
                                                  + " , ORIGIN_AIRPORT_ID=" + flight[7] 
                                                  + "]");
                        }
                        br.close();
                        return "You successfully uploaded " + name;
                  } catch (Exception e) {
                        return "You failed to upload " + name + " => " + e.getMessage();
                  }
            } else {
                  return "You failed to upload " + name + " because the file was empty.";
              }
       }
}

Une nouvelle méthode handleFileUpload() répond à la requête POST et lit le flux de téléchargement ligne par ligne. Chaque ligne est analysée et un objet Key et plusieurs objets Bin sont construits pour former un enregistrement (record) Aerospike. Enfin, la méthode aerospike put() est appelée pour stocker l'enregistrement dans le cluster Aerospike.

Une autre nouvelle méthode provideUploadInfo() répond à une requête GET et renvoie un message indiquant que des ajouts sont possibles.

Télécharger l'application cliente

Le chargement peut être fait comme vous le voulez. Mais vous pouvez utiliser la classe suivante pour charger les données vers le service.

src/test/java/com.aerospike.client.rest/FlightsUploader.java
package com.aerospike.client.rest;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.FileSystemResource;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

public class FilghtsUploader {
      private static final String TEST_FILE = "flights_from.csv";
      @Before
      public void setUp() throws Exception {
      }

      @Test
      public void upload() {
        RestTemplate template = new RestTemplate();
        MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
        parts.add("name", TEST_FILE);
        parts.add("file", new FileSystemResource(TEST_FILE));
        String response = template.postForObject("http://localhost:8080/uploadFlights",
                                                  parts, String.class);
        System.out.println(response);
      }
}

Données de vols

Ce sont des données réelles depuis 2012. Elles représentent approximativement 1 million d'enregistrements, donc soyez conscients que cela prendra quelques minutes à charger.

Construire et exécuter le Service

Le fichier pom.xml pour Maven va packager le service dans un unique fichier jar. Utilisez la commande suivante :

mvn clean package

Cela va générer une application autonome de web service dans un jar exécutable dans le sous-répertoire target. Ce fichier jar contient une instance de Tomcat, donc vous pouvez simplement l'exécuter sans installer un serveur d'application :

java -jar aerospike-restful-example-1.0.0.jar

Pour conclure

Félicitations ! Vous venez de développer un service RESTful simple avec Spring et en le connectant à un cluster Aerospike.

Le code complet

Code d’exemple

Considérations sur la conception

Le contrôle d'accès est actuellement géré par l'application versus la base de données. Comme le processus d'authentification ralentit la vitesse de base de données, pratiquement toutes les bases NoSQL ne supportent pas cette fonction. La plupart de nos clients donnent la priorité à une vitesse accrue plutôt qu'à une fonction d'authentification intégrée.

Une autre caractéristique souvent demandée est une jointure de deux ensembles différents de données. C'est un défi à relever pour toutes les bases de données distribuées, car les données du join (jointure) sont distribuées. Dans ce cas, le développeur doit mettre en oeuvre une jointure dans la requête.

A propos de l'Auteur

Peter Milne est un professionnel expérimenté de l'IT avec une vaste expérience dans le développement de logiciel complet et les cycles de vie des produits. Il a des compétences techniques et une expérience de gestion avec des équipes de développement de toutes tailles. Peter était récemment Architecte de solutions senior chez Aerospike. Avant cela, il a été Analyste principal et Programmeur chez MLC, et a été Directeur technique chez iTerative Consulting pour construire un outil Java de conversion Forte/UDS avec 99,999% de précision. Peter est titulaire d'une maîtrise en informatique distribuée de l'Université de Technologie de Sydney, ainsi que de plusieurs certificats de sécurité pour hélicoptères.

Bonjour étranger!

Vous devez créer un compte InfoQ ou cliquez sur pour déposer des commentaires. Mais il y a bien d'autres avantages à s'enregistrer.

Tirez le meilleur d'InfoQ

Donnez-nous votre avis

Html autorisé: a,b,br,blockquote,i,li,pre,u,ul,p

M'envoyer un email pour toute réponse à l'un de mes messages dans ce sujet
Commentaires de la Communauté

Html autorisé: a,b,br,blockquote,i,li,pre,u,ul,p

M'envoyer un email pour toute réponse à l'un de mes messages dans ce sujet

Html autorisé: a,b,br,blockquote,i,li,pre,u,ul,p

M'envoyer un email pour toute réponse à l'un de mes messages dans ce sujet

Discuter

Contenu Éducatif

Rien ne serait possible sans le soutien et la confiance de nos Sponsors Fondateurs:

AppDynamics   CloudBees   Microsoft   Zenika
Feedback Général
Bugs
Publicité
Éditorial
InfoQ.com et tous les contenus sont copyright © 2006-2014 C4Media Inc. InfoQ.com est hébergé chez Contegix, le meilleur ISP avec lequel nous ayons travaillé.
Politique de confidentialité
BT