Creazione di un'API con Spark Java Framework

1. Introduzione

In questo articolo, avremo una rapida introduzione al framework Spark. Spark framework è un framework web di rapido sviluppo ispirato al framework Sinatra per Ruby ed è costruito intorno alla filosofia Java 8 Lambda Expression, rendendolo meno prolisso della maggior parte delle applicazioni scritte in altri framework Java.

È una buona scelta se vuoi avere un'esperienza simile a Node.js durante lo sviluppo di un'API web o di microservizi in Java. Con Spark, puoi avere un'API REST pronta per servire JSON in meno di dieci righe di codice.

Inizieremo rapidamente con un esempio "Hello World", seguito da una semplice API REST.

2. Dipendenze di Maven

2.1. Spark Framework

Includi la seguente dipendenza Maven nel tuo pom.xml :

 com.sparkjava spark-core 2.5.4 

È possibile trovare l'ultima versione di Spark su Maven Central.

2.2. Libreria Gson

In vari punti dell'esempio, utilizzeremo la libreria Gson per le operazioni JSON. Per includere Gson nel tuo progetto, includi questa dipendenza nel tuo pom.xml :

 com.google.code.gson gson 2.8.0 

È possibile trovare l'ultima versione di Gson su Maven Central.

3. Introduzione a Spark Framework

Diamo un'occhiata agli elementi costitutivi di base di un'applicazione Spark e dimostriamo un servizio Web rapido.

3.1. Itinerari

I servizi Web in Spark Java sono costruiti sulle rotte e sui relativi gestori. I percorsi sono elementi essenziali in Spark. Secondo la documentazione, ogni rotta è composta da tre semplici parti: un verbo , un percorso e un callback .

  1. Il verbo è un metodo corrispondente a un metodo HTTP. I metodi verb includono: get, post, put, delete, head, trace, connect e options
  2. Il percorso (chiamato anche modello di rotta) determina quali URI la rotta deve ascoltare e per cui fornire una risposta
  3. Il callback è una funzione del gestore che viene invocata per un determinato verbo e percorso al fine di generare e restituire una risposta alla richiesta HTTP corrispondente. Un callback accetta un oggetto richiesta e un oggetto risposta come argomenti

Qui mostriamo la struttura di base per una rotta che utilizza il verbo get :

get("/your-route-path/", (request, response) -> { // your callback code });

3.2. Hello World API

Creiamo un semplice servizio web che abbia due route per le richieste GET e restituisca messaggi "Hello" in risposta. Queste rotte usano il metodo get , che è un'importazione statica dalla classe spark.Spark :

import static spark.Spark.*; public class HelloWorldService { public static void main(String[] args) { get("/hello", (req, res)->"Hello, world"); get("/hello/:name", (req,res)->{ return "Hello, "+ req.params(":name"); }); } }

Il primo argomento del metodo get è il percorso della rotta. La prima rotta contiene un percorso statico che rappresenta solo un singolo URI ( "/ hello" ).

Il percorso della seconda rotta ( "/ hello /: name" ) contiene un segnaposto per il parametro "name" , come indicato anteponendo al parametro i due punti (":"). Questa route verrà invocata in risposta alle richieste GET a URI come "/ hello / Joe" e "/ hello / Mary" .

Il secondo argomento del metodo get è un'espressione lambda che conferisce un sapore di programmazione funzionale a questo framework.

L'espressione lambda ha richiesta e risposta come argomenti e aiuta a restituire la risposta. Metteremo la logica del nostro controller nell'espressione lambda per le rotte API REST, come vedremo più avanti in questo tutorial.

3.3. Test dell'API Hello World

Dopo aver eseguito la classe HelloWorldService come una normale classe Java, sarai in grado di accedere al servizio sulla sua porta predefinita di 4567 utilizzando le rotte definite con il metodo get sopra.

Diamo un'occhiata alla richiesta e alla risposta per il primo percorso:

Richiesta:

GET //localhost:4567/hello

Risposta:

Hello, world

Proviamo il secondo percorso, passando il parametro name nel suo percorso:

Richiesta:

GET //localhost:4567/hello/baeldung

Risposta:

Hello, baeldung

Guarda come il posizionamento del testo "baeldung" nell'URI è stato utilizzato per abbinare il pattern di route "/ hello /: name" , provocando l'invocazione della funzione callback handler della seconda rotta.

4. Progettazione di un servizio RESTful

In questa sezione, progetteremo un semplice servizio Web REST per la seguente entità utente :

public class User { private String id; private String firstName; private String lastName; private String email; // constructors, getters and setters }

4.1. Itinerari

Elenchiamo le rotte che compongono la nostra API:

  • GET / users - ottiene l'elenco di tutti gli utenti
  • GET / users /: id - ottiene l'utente con un dato id
  • POST / users /: id - aggiunge un utente
  • PUT / users /: id - modifica un particolare utente
  • OPZIONI / users /: id - controlla se esiste un utente con un dato id
  • DELETE / users /: id - elimina un particolare utente

4.2. Il servizio utente

Below is the UserService interface declaring the CRUD operations for the User entity:

public interface UserService { public void addUser (User user); public Collection getUsers (); public User getUser (String id); public User editUser (User user) throws UserException; public void deleteUser (String id); public boolean userExist (String id); }

For demonstration purposes, we provide a Map implementation of this UserService interface in the GitHub code to simulate persistence. You can supply your own implementation with the database and persistence layer of your choice.

4.3. The JSON Response Structure

Below is the JSON structure of the responses used in our REST service:

{ status:  message:  data:  }

The status field value can be either SUCCESS or ERROR. The data field will contain the JSON representation of the return data, such as a User or collection of Users.

When there is no data being returned, or if the status is ERROR, we will populate the message field to convey a reason for the error or lack of return data.

Let's represent the above JSON structure using a Java class:

public class StandardResponse { private StatusResponse status; private String message; private JsonElement data; public StandardResponse(StatusResponse status) { // ... } public StandardResponse(StatusResponse status, String message) { // ... } public StandardResponse(StatusResponse status, JsonElement data) { // ... } // getters and setters }

where StatusResponse is an enum defined as below:

public enum StatusResponse { SUCCESS ("Success"), ERROR ("Error"); private String status; // constructors, getters }

5. Implementing RESTful Services

Now let's implement the routes and handlers for our REST API.

5.1. Creating Controllers

The following Java class contains the routes for our API, including the verbs and paths and an outline of the handlers for each route:

public class SparkRestExample { public static void main(String[] args) { post("/users", (request, response) -> { //... }); get("/users", (request, response) -> { //... }); get("/users/:id", (request, response) -> { //... }); put("/users/:id", (request, response) -> { //... }); delete("/users/:id", (request, response) -> { //... }); options("/users/:id", (request, response) -> { //... }); } }

We will show the full implementation of each route handler in the following subsections.

5.2. Add User

Below is the post method response handler which will add a User:

post("/users", (request, response) -> { response.type("application/json"); User user = new Gson().fromJson(request.body(), User.class); userService.addUser(user); return new Gson() .toJson(new StandardResponse(StatusResponse.SUCCESS)); });

Note: In this example, the JSON representation of the User object is passed as the raw body of a POST request.

Let's test the route:

Request:

POST //localhost:4567/users { "id": "1012", "email": "[email protected]", "firstName": "Mac", "lastName": "Mason1" }

Response:

{ "status":"SUCCESS" }

5.3. Get All Users

Below is the get method response handler which returns all users from the UserService:

get("/users", (request, response) -> { response.type("application/json"); return new Gson().toJson( new StandardResponse(StatusResponse.SUCCESS,new Gson() .toJsonTree(userService.getUsers()))); });

Now let's test the route:

Request:

GET //localhost:4567/users

Response:

{ "status":"SUCCESS", "data":[ { "id":"1014", "firstName":"John", "lastName":"Miller", "email":"[email protected]" }, { "id":"1012", "firstName":"Mac", "lastName":"Mason1", "email":"[email protected]" } ] }

5.4. Get User by Id

Below is the get method response handler which returns a User with the given id:

get("/users/:id", (request, response) -> { response.type("application/json"); return new Gson().toJson( new StandardResponse(StatusResponse.SUCCESS,new Gson() .toJsonTree(userService.getUser(request.params(":id"))))); });

Now let's test the route:

Request:

GET //localhost:4567/users/1012

Response:

{ "status":"SUCCESS", "data":{ "id":"1012", "firstName":"Mac", "lastName":"Mason1", "email":"[email protected]" } }

5.5. Edit a User

Below is the put method response handler, which edits the user having the id supplied in the route pattern:

put("/users/:id", (request, response) -> { response.type("application/json"); User toEdit = new Gson().fromJson(request.body(), User.class); User editedUser = userService.editUser(toEdit); if (editedUser != null) { return new Gson().toJson( new StandardResponse(StatusResponse.SUCCESS,new Gson() .toJsonTree(editedUser))); } else { return new Gson().toJson( new StandardResponse(StatusResponse.ERROR,new Gson() .toJson("User not found or error in edit"))); } });

Note: In this example, the data are passed in the raw body of a POST request as a JSON object whose property names match the fields of the User object to be edited.

Let's test the route:

Request:

PUT //localhost:4567/users/1012 { "lastName": "Mason" }

Response:

{ "status":"SUCCESS", "data":{ "id":"1012", "firstName":"Mac", "lastName":"Mason", "email":"[email protected]" } }

5.6. Delete a User

Below is the delete method response handler, which will delete the User with the given id:

delete("/users/:id", (request, response) -> { response.type("application/json"); userService.deleteUser(request.params(":id")); return new Gson().toJson( new StandardResponse(StatusResponse.SUCCESS, "user deleted")); });

Now, let's test the route:

Request:

DELETE //localhost:4567/users/1012

Response:

{ "status":"SUCCESS", "message":"user deleted" }

5.7. Check if User Exists

The options method is a good choice for conditional checking. Below is the options method response handler which will check whether a User with the given id exists:

options("/users/:id", (request, response) -> { response.type("application/json"); return new Gson().toJson( new StandardResponse(StatusResponse.SUCCESS, (userService.userExist( request.params(":id"))) ? "User exists" : "User does not exists" )); });

Now let's test the route:

Request:

OPTIONS //localhost:4567/users/1012

Response:

{ "status":"SUCCESS", "message":"User exists" }

6. Conclusion

In questo articolo, abbiamo avuto una rapida introduzione al framework Spark per uno sviluppo web rapido.

Questo framework è principalmente promosso per la generazione di microservizi in Java. Gli sviluppatori di Node.js con conoscenza di Java che desiderano sfruttare le librerie costruite su librerie JVM dovrebbero sentirsi a casa usando questo framework.

E come sempre, puoi trovare tutte le fonti per questo tutorial nel progetto Github.