• Programmazione Android
  • CORSI ONLINE
  • Web Agency

Logo

Corsi di programmazione web e mobile online
Navigation
  • Home
  • CORSI ONLINE
  • Tutorial Pratici
  • GUIDE COMPLETE
    • Corso completo di C
    • Corso videogame con Cocos2d
    • Programmazione Cocoa Touch
  • Sezioni
    • Libri e manuali
    • Tips & Tricks
    • Risorse utili
    • Strumenti di Sviluppo
    • Materiale OpenSource
    • Framework
    • Guide Teoriche
    • Guide varie
    • Grafica e Design
    • iPad
    • News
    • Video Tutorial
    • Windows Phone
  • Pubblicità
  • About
    • Chi siamo
    • Pubblicazioni
    • Collabora
    • Sostieni devAPP

Creare API REST con Spark Java, Gson e RethinkDB

By Giuseppe Maggi | on 26 Maggio 2017 | 0 Comment
Senza categoria
REST API Java Tutorial

Al giorno d’oggi esistono molti servizi moderni, affidabili e reattivi, basati su programmi di piccole dimensioni e dalla configurazione agevole. Ne abbiamo già conosciuti alcuni su questo sito come Spark Java che permette di creare velocemente API REST e RethinkDB che offre database JSON ideali per funzionalità real-time. In questo articolo, li metteremo insieme facendoci aiutare anche da Gson, altra efficacissima libreria per il formato JSON offerta da Google: realizzeremo un’applicazione di esempio il cui codice potrà essere scaricato qui.

Cosa vogliamo realizzare?

Il nostro scopo è quello di gestire un database RethinkDB tramite il suo driver per Java. Per il resto, Spark Java ci offrirà gli strumenti per l’interfacciamento in rete e tutto il codice di gestione dei dati sarà racchiuso nella nostra classe LocalDataService. Il database sarà composto da una sola tabella la cui finalità sarà conservare due stringhe – text1 e text2 – ad uso molto generico (potrebbe trattarsi di una nota con il suo contenuto o altro simile) nonchè un id univoco in formato stringa assegnato da RethinkDB.

La prima cosa che ci serve è risolvere le dipendenze. Il nostro progetto avrà bisogno delle tre librerie che abbiamo nominato per cui potremo scaricare i relativi file jar dai siti ufficiali o ricorrere ad un sistema di gestione come Maven. Noi, ad esempio, abbiamo percorso quest’ultima via creando un progetto Maven per Eclipse nel cui file pom.xml abbiamo inserito le seguenti dipendenze:

<dependencies>
    <dependency>
      <groupId>com.google.code.gson </groupId>
      <artifactId>gson</artifactId>
      <version>2.7</version>
    </dependency>
    <dependency>
      <groupId>com.sparkjava</groupId>
      <artifactId>spark-core</artifactId>
      <version>2.5</version>
    </dependency>
    <dependency>
      <groupId>com.rethinkdb</groupId>
      <artifactId>rethinkdb-driver</artifactId>
      <version>2.3.3</version>
    </dependency>
</dependencies>

Un database con RethinkDB

Per eseguire l’applicazione, è necessario installare RethinkDB. Il procedimento è rapidissimo ed una volta lanciato il server si può invocare da browser l’indirizzo http://localhost:8080/ per accedere al suo pannello di amministrazione. Gestiremo il rapporto con questa sorgente dati tramite la classe Connector:

public class Connector {
	private String dbname="test";
	private String hostname="localhost";
	private int port=28015;
	private String tableName;
	private Connection conn=null;
	private RethinkDB r =null;
	
	public Connector(String db, String tableName)
	{
		dbname=db;
		this.tableName=tableName;
		r= RethinkDB.r;
		try{
	    	r.dbCreate(dbname).run(getConnection());
	    	r.db(dbname).tableCreate(tableName).run(getConnection());
	    	}
	    catch(ReqlOpFailedError e){}
	}
	
	public Connection getConnection()
	{
	  if (conn==null || !conn.isOpen())
	   conn = r.connection().hostname(hostname).port(port).connect();
	  return conn;
	}
	
	public Table getTable()
	{
		return r.db(dbname).table(tableName);
	}
	
}

Tutta la gestione dei dati verrà effettuata tramite l’oggetto r, di classe RethinkDB, che rappresenta la nostra porta d’accesso alla sorgente.

In questa classe:

  • il metodo getConnection aprirà una connessione con RethinkDB, via rete, sfruttando hostname e porta TCP che abbiamo definito nei membri privati;
  • all’interno del costruttore creiamo il database con la sua unica tabella: potrebbero già esistere entrambi e ciò genererebbe un’eccezione da cui ci tuteliamo con un blocco try…catch;
  • getTable offrirà un collegamento alla tabella.

Il Connector serve prevalentemente a stabilire connessione al database e verrà utilizzata esclusivamente dalla classe LocalDataService. All’interno di quest’ultima ne definiamo un’altra, DataNode, che rappresenta il singolo record della tabella (per la precisione, dovremmo chiamarlo “documento” in quanto stiamo parlando di una tecnologia NoSQL):

public class LocalDataService {
	
	public static class DataNode
	{
		String id;
		String text1;
		String text2;
		
		DataNode(String t1, String t2)
		{
			text1=t1;
			text2=t2;
		}
		
		DataNode(String id, String t1, String t2)
		{
			this(t1,t2);
			this.id=id;
		}
		
		MapObject toMapObject()
		{
			MapObject res=new MapObject();
			if (text1!=null)
				res.put("text1", text1);
			if (text2!=null)
				res.put("text2", text2);
			return res;
		}
		
	}

     /*
      *
      *  metodi di accesso ai dati della classe
      *
      */
}

vi abbiamo inserito anche il metodo toMapObject che esporta i dati in un tipo di struttura dati a mappa utilizzata da RethinkDB.

public class LocalDataService {
	
       /*
        *
        * definizione della classe DataNode
        *
        */

	private Connector conn;
	private boolean valid;
	
	public boolean isValid() {
		return valid;
	}

	public LocalDataService(String db, String tableName)
	{
		try{
		conn=new Connector(db, tableName);
		valid=true;
		}
		catch(ReqlDriverError rde)
		{
			valid=false;
		}
	}
	
	public HashMap<String, String> save(DataNode dn)
	{
		HashMap<String, Object> result=conn.getTable().insert(dn.toMapObject()).run(conn.getConnection());
		if ((Long)result.get("inserted")==1)
		{
			ArrayList<String> l=(ArrayList<String>) result.get("generated_keys");
			if (l.size()>0)
			{
				HashMap<String, String> res=new HashMap<String, String>();
				res.put("key", l.get(0));
			    return res;
			}
		}
		throw new RuntimeException();
	}
	
	public HashMap<String, Integer> tableSize()
	{
		Cursor cursor=conn.getTable().run(conn.getConnection());
		int size=cursor.toList().size();
		HashMap<String, Integer> res=new HashMap<String, Integer>();
		res.put("size", size);
	    return res;
	}
	
	public Object tableElement(String key)
	{
		Object o=conn.getTable().get(key).run(conn.getConnection());
		if (o==null)
			throw new NotValidKeyException(key);
	    return o;
	}
	
	public Object deleteElement(String key)
	{
		HashMap<String,Long> o=conn.getTable().get(key).delete().run(conn.getConnection());
		HashMap<String,String> res=new HashMap<String,String>();
		if (o.get("deleted")==1)
		{
			res.put("key", key);
			res.put("deleted", "true");
			return res;
		}
		else if (o.get("deleted")==0 && o.get("skipped")==1)
			throw new NotValidKeyException(key);
		throw new InternalServerException("Internal Server Exception");
	}
	
	public HashMap<String,String> updateElement(String key, DataNode dn)
	{
		HashMap<String,Long> o=conn.getTable().get(key).update(dn.toMapObject()).run(conn.getConnection());
		HashMap<String,String> res=new HashMap<String,String>();
		if (o.get("replaced")==1)
		{
			res.put("key", key);
			res.put("updated", "true");
			return res;
		}
		else if (o.get("skipped")==1)
			throw new NotValidKeyException(key);
		else
			if (o.get("unchanged")==1)
			{
				res.put("key", key);
				res.put("updated", "false");
				return res;
			}
		throw new InternalServerException("Internal Server Exception");
	}
}

Il costruttore della classe inizializza un oggetto Connector il cui fallimento renderebbe impossibile l’utilizzo delle API: questo può, ad esempio, succedere se non abbiamo avviato RethinkDB o se quest’ultimo ha subito un crash. Il membro booleano valid serve proprio al codice che sfrutta Spark Java per sapere se il DataService è attivo: in caso negativo, come vedremo, le API verranno arrestate all’avvio.

Gli altri metodi sono quelli che vengono invocati dalle API REST e sono esemplificativi dei vari possibili casi: lettura, scrittura, cancellazione e modifica. Ognuno di essi, in base all’esito dell’elaborazione, dovrà produrre un risultato del tipo richiesto che poi tradurremo in JSON ed invieremo al client o un’opportuna eccezione, utilizzata anch’essa per rispondere alla richiesta. Richiederemo sempre un riferimento alla tabella tramite il Connector e poi eseguiremo un apposito metodo di ricerca o modifica. Queste invocazioni termineranno sempre con la chiamata a run che vuole un riferimento alla connessione valida, fornito dal metodo getConnection del Connector. Vediamoli più in dettaglio:

  • tableSize e tableElement sono i due metodi di lettura: il primo restituisce un numero intero indicante quanti sono i record al momento trattati dal database mentre il secondo restituisce una HashMap che descrive il documento recuperato in base alla chiave. Si noti che entrambi richiedono un riferimento alla tabella dal Connector;
  • deleteElement cerca un documento contenente la chiave fornita e, se rinvenuto, ne ordina la cancellazione. La risposta di RethinkDB sarà un oggetto JSON in cui la proprietà deleted impostata a 1 indicherà cancellazione avvenuta mentre skipped a 1 significherà che la chiave non è stata trovata;
  • save riceve un oggetto DataNode e lo inserisce nella tabella tramite metodo insert. E’ a questo punto che utilizziamo il metodo di conversione toMapObject di DataNode. Importante in questo caso recuperare la chiave appena generata che restituiremo al client che potrebbe farne uso per aggiornare i suoi dati  o l’interfaccia utente se ne possiede una;
  • updateElement non è molto differente dai precedenti: grazie al driver di RethinkDB cerchiamo per chiave il documento da modificare e gli forniamo un oggetto DataNode (anche parzialmente compilato) per vedere cosa c’è da modificare. In questo caso, è più articolato valutare l’esito dell’operazione. Ci sono tre flag attivati nell’oggetto JSON risultato e li valutiamo tutti: replaced vale 1 se siamo riusciti ad apportare modifiche, unchanged in caso contrario e skipped se non si è riuscito proprio a svolgere l’operazione.

Le eccezioni richiamate in questo codice sono state definite da noi nel package exceptions e ciò serve solo a comprendere meglio i risultati delle interrogazioni sul database.

Finalmente le API REST

Come si utilizzano le API REST con Spark Java è stato già oggetto del nostro precedente articolo, come ricordato in precedenza. E’ necessario invocare un metodo (il cui nome coincide con quello del verbo HTTP cui ci riferiamo) e fornire pattern per l’URL che risponderà, il codice di reazione da attivare ed eventualmente – noi lo facciamo in questo caso – un ResponseTransformer che formatta la risposta prima di restituirla al client: all’interno useremo Gson per convertire i dati in JSON:

public class JsonTransformer implements ResponseTransformer {

	private Gson gson;

    public JsonTransformer(Gson gson) {
		this.gson=gson;
	}
    @Override
    public String render(Object model) {
        return gson.toJson(model);
    }
    
    public String renderError(String message) {
        HashMap<String, String>  error=new HashMap<String, String>();
    	error.put("error", message);
    	return render(error);
    }

}

Ai fini della trasformazione in JSON delle risposte al client, Spark Java invocherà sempre il metodo render in automatico di cui abbiamo fatto override mentre l’altro, renderError, lo usiamo noi esplicitamente per formattare i messaggi di errore.

Il codice è suddiviso in due porzioni: prima definiamo i gestori di eccezioni per ognuno dei casi di errore che abbiamo riscontrato nel LocalDataService e poi attiviamo tutti metodi HTTP che ci interessano:

public class App {
    public static void main( String[] args) {
    	LocalDataService ds=new LocalDataService("elenco", "dati");
    	
    	if (!ds.isValid())
    	{
    		System.out.println("Error: Connection to backed service failed");
    		System.out.println("Server stopped");
    		System.exit(-1);
    	}
    	System.out.println("Server running...");
    	
    	Gson gson=new Gson();
    	JsonTransformer transformer=new JsonTransformer(gson);
    	
    	String BASE_URL="/textbase/v1";
    	port(1121);

// gestione eccezioni
    	
    	exception(InternalServerException.class, (exception, request, response) -> {
    		response.status(501);
			response.body(transformer.renderError(exception.getMessage()));
    	});
    	
    	exception(ResourceNotFoundException.class, (exception, request, response) -> {
    		response.status(404);
			response.body(transformer.renderError(exception.getMessage()));
    	});
    	
    	exception(NotValidKeyException.class, (exception, request, response) -> {
    		response.status(400);
			response.body(transformer.renderError(exception.getMessage()));
    	});
    	
    	exception(NotValidDataException.class, (exception, request, response) -> {
    		response.status(400);
			response.body(transformer.renderError(exception.getMessage()));
    	});


// chiamate REST
    	
    	get(BASE_URL+"/data/count", "application/json", (request, response) -> {
    	    return ds.tableSize();
    	}, transformer);
    	
    	
    	get(BASE_URL+"/data/:key", "application/json", (req, res) -> {
    		String key=req.params(":key");
            return ds.tableElement(key);
        }, transformer);
    	
    	post(BASE_URL+"/data/add", "application/json", (request, response) -> {
    		DataNode newItem=gson.fromJson(request.body(), DataNode.class);
    		response.status(201);
    	    return ds.save(newItem);
    	}, transformer);
    	
    	delete(BASE_URL+"/data/:key", "application/json", (req, res) -> {
    		String key=req.params(":key");
    		return ds.deleteElement(key);
        }, transformer);
    	
    	put(BASE_URL+"/data/:key", "application/json", (req, res) -> {
    		String key=req.params(":key");
    		if (req.body().length()==0)
    			throw new NotValidDataException("No data sent in request body");
    		DataNode dataToBeUpdated=gson.fromJson(req.body(), DataNode.class);
    		return ds.updateElement(key, dataToBeUpdated);
        }, transformer);
    	
    	
    }
}

Conclusioni

Con questo esempio, abbiamo voluto dimostrare quanto servizi moderni – non tanto come epoca di nascita ma come “mentalità” – possano rendere agevole la realizzazione di API al fine di offrire un backend server alle nostre app mobile. In un post già apparso su questo sito, abbiamo parlato dell’attuale tendenza alla realizzazione di applicativi server basati su microservizi piuttosto che su architetture monolitiche: Spark Java è uno strumento ideale a contesti simili e se ne dimostra continuamente all’altezza.

Continueremo ad aggiornarvi su queste interessanti tematiche: non mancate di seguirci e farci sapere le vostre opinioni.

Alla prossima!

 

Share this story:
  • tweet

Tags: Databasejavamicroservicesnosql databaserest apiRethinkDBspark javaTutorial Pratici

Recent Posts

  • Parte il percorso programmatori iOS in Swift su devACADEMY.it

    20 Dicembre 2017 - 0 Comment
  • Android, crittografare dati velocemente con Encryption

    24 Settembre 2018 - 0 Comment
  • Sql2o, accesso immediato ai database tramite Java

    3 Settembre 2018 - 0 Comment
  • Okio, libreria per ottimizzare l’input/output in Java

    27 Agosto 2018 - 0 Comment

Related Posts

  • Sql2o, accesso immediato ai database tramite Java

    3 Settembre 2018 - 0 Comment
  • Okio, libreria per ottimizzare l’input/output in Java

    27 Agosto 2018 - 0 Comment
  • Stringhe in Kotlin

    23 Agosto 2018 - 0 Comment

Author Description

No Responses to “Creare API REST con Spark Java, Gson e RethinkDB”

Leave a Reply

Your email address will not be published. Required fields are marked *


*
*

Corso online di programmazione android e java

SEZIONI

  • Android
  • Comunicazioni
  • Contest
  • Corsi ed Eventi
  • Corso completo di C
  • Corso programmazione videogiochi
  • Framework
  • Grafica e Design
  • Guida rapida alla programmazione Cocoa Touch
  • Guide Teoriche
  • Guide varie
  • iPad
  • Le nostre applicazioni
  • Libri e manuali
  • Materiale OpenSource
  • News
  • Pillole di C++
  • Progetti completi
  • Risorse utili
  • Strumenti di Sviluppo
  • Swift
  • Tips & Tricks
  • Tutorial Pratici
  • Video Tutorial
  • Windows Phone

Siti Amici

  • Adrirobot
  • Allmobileworld
  • Apple Notizie
  • Apple Tribù
  • Avvocato360
  • Blog informatico 360°
  • bubi devs
  • fotogriPhone
  • GiovaTech
  • iApp-Mac
  • iOS Developer Program
  • iPodMania
  • MelaRumors
  • Meritocracy
  • SoloTablet
  • TecnoUser
  • Privacy & Cookie Policy
©2009-2018 devAPP - All Rights Reserved | Contattaci
devAPP.it è un progetto di DEVAPP S.R.L. - Web & Mobile Agency di Torino
Str. Volpiano, 54 - 10040 Leini (TO) - C.F. e P.IVA 11263180017 - REA TO1199665 - Cap. Soc. € 10.000,00 i.v.

devACADEMY.it

Vuoi imparare a programmare?

Iscriviti e accedi a TUTTI i corsi con un’unica iscrizione.
Oltre 70 corsi e migliaia di videolezioni online e in italiano a tua disposizione.

ISCRIVITI SUBITO