Erstellen einer API mit dem Spark Java Framework

1. Einleitung

In diesem Artikel erhalten Sie eine kurze Einführung in das Spark-Framework. Das Spark-Framework ist ein Web-Framework für die schnelle Entwicklung, das vom Sinatra-Framework für Ruby inspiriert ist und auf der Java 8 Lambda Expression-Philosophie basiert. Es ist weniger ausführlich als die meisten Anwendungen, die in anderen Java-Frameworks geschrieben wurden.

Dies ist eine gute Wahl, wenn Sie Erfahrung mit Node.js bei der Entwicklung einer Web-API oder von Microservices in Java haben möchten . Mit Spark können Sie eine REST-API bereithalten, um JSON in weniger als zehn Codezeilen bereitzustellen.

Wir werden einen schnellen Start mit einem "Hello World" -Beispiel haben, gefolgt von einer einfachen REST-API.

2. Maven-Abhängigkeiten

2.1. Spark Framework

Nehmen Sie die folgende Maven-Abhängigkeit in Ihre pom.xml auf :

 com.sparkjava spark-core 2.5.4 

Sie finden die neueste Version von Spark in Maven Central.

2.2. Gson Bibliothek

An verschiedenen Stellen im Beispiel verwenden wir die Gson-Bibliothek für JSON-Operationen. Um Gson in Ihr Projekt aufzunehmen, fügen Sie diese Abhängigkeit in Ihre pom.xml ein :

 com.google.code.gson gson 2.8.0 

Sie finden die neueste Version von Gson auf Maven Central.

3. Erste Schritte mit Spark Framework

Lassen Sie uns einen Blick auf die Grundbausteine ​​einer Spark-Anwendung werfen und einen schnellen Webdienst demonstrieren.

3.1. Routen

Webdienste in Spark Java basieren auf Routen und ihren Handlern. Routen sind wesentliche Elemente in Spark. Gemäß der Dokumentation besteht jede Route aus drei einfachen Teilen - einem Verb , einem Pfad und einem Rückruf .

  1. Das Verb ist eine Methode, die einer HTTP-Methode entspricht. Zu den Verbmethoden gehören: Get, Post, Put, Delete, Head, Trace, Connect und Optionen
  2. Der Pfad (auch als Routenmuster bezeichnet) bestimmt, auf welche URI (s) die Route hören und eine Antwort geben soll
  3. Der Rückruf ist eine Handlerfunktion, die für ein bestimmtes Verb und einen bestimmten Pfad aufgerufen wird, um eine Antwort auf die entsprechende HTTP-Anforderung zu generieren und zurückzugeben. Ein Rückruf verwendet ein Anforderungsobjekt und ein Antwortobjekt als Argumente

Hier zeigen wir die Grundstruktur für eine Route, die das Verb get verwendet :

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

3.2. Hallo Welt API

Erstellen wir einen einfachen Webdienst, der zwei Routen für GET-Anforderungen enthält und als Antwort "Hallo" -Nachrichten zurückgibt. Diese Routen verwenden die get- Methode, bei der es sich um einen statischen Import aus der Klasse spark.Spark handelt :

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"); }); } }

Das erste Argument für die get- Methode ist der Pfad für die Route. Die erste Route enthält einen statischen Pfad, der nur einen einzelnen URI darstellt ( „/ hello“ ).

Der Pfad der zweiten Route ( "/ hello /: name" ) enthält einen Platzhalter für den Parameter "name" , der durch das Voranstellen eines Doppelpunkts (":") angegeben wird. Diese Route wird als Antwort auf GET-Anfragen an URIs wie "/ hello / Joe" und "/ hello / Mary" aufgerufen .

Das zweite Argument für die get- Methode ist ein Lambda-Ausdruck, der diesem Framework eine funktionale Programmiervariante verleiht.

Der Lambda-Ausdruck hat Anforderung und Antwort als Argumente und hilft, die Antwort zurückzugeben. Wir werden unsere Controller-Logik in den Lambda-Ausdruck für die REST-API-Routen einfügen, wie wir später in diesem Tutorial sehen werden.

3.3. Testen der Hello World API

Nachdem Sie die Klasse HelloWorldService als normale Java-Klasse ausgeführt haben, können Sie über die mit der obigen Methode get definierten Routen über den Standardport 4567 auf den Dienst zugreifen .

Schauen wir uns die Anfrage und Antwort für die erste Route an:

Anfrage:

GET //localhost:4567/hello

Antwort:

Hello, world

Testen wir die zweite Route, indem wir den Parameter name in ihrem Pfad übergeben:

Anfrage:

GET //localhost:4567/hello/baeldung

Antwort:

Hello, baeldung

Sehen Sie, wie die Platzierung des Textes "baeldung" in der URI verwendet wurde, um mit dem Routenmuster " / hello /: name " übereinzustimmen, wodurch die Callback-Handler-Funktion der zweiten Route aufgerufen wurde.

4. Entwerfen eines RESTful-Service

In diesem Abschnitt entwerfen wir einen einfachen REST-Webdienst für die folgende Benutzerentität :

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

4.1. Routen

Lassen Sie uns die Routen auflisten, aus denen unsere API besteht:

  • GET / users - Liste aller Benutzer abrufen
  • GET / users /: id - Benutzer mit der angegebenen ID abrufen
  • POST / users /: id - Benutzer hinzufügen
  • PUT / users /: id - Bearbeiten Sie einen bestimmten Benutzer
  • OPTIONEN / Benutzer /: ID - Überprüfen Sie, ob ein Benutzer mit der angegebenen ID vorhanden ist
  • DELETE / users /: id - Löscht einen bestimmten Benutzer

4.2. Der Benutzerdienst

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 diesem Artikel hatten wir eine kurze Einführung in das Spark-Framework für eine schnelle Webentwicklung.

Dieses Framework wird hauptsächlich für die Generierung von Microservices in Java gefördert. Entwickler von Node.j mit Java-Kenntnissen, die auf JVM-Bibliotheken basierende Bibliotheken nutzen möchten, sollten sich mit diesem Framework wie zu Hause fühlen.

Und wie immer finden Sie alle Quellen für dieses Tutorial im Github-Projekt.