Membangun API Dengan Kerangka Kerja Spark Java

1. Perkenalan

Pada artikel ini, kami akan memiliki pengenalan singkat tentang kerangka kerja Spark. Kerangka kerja Spark adalah kerangka kerja web yang berkembang pesat yang terinspirasi oleh kerangka kerja Sinatra untuk Ruby dan dibangun dengan filosofi Java 8 Lambda Expression, membuatnya kurang bertele-tele daripada kebanyakan aplikasi yang ditulis dalam kerangka kerja Java lainnya.

Ini adalah pilihan yang baik jika Anda ingin memiliki pengalaman seperti Node.js saat mengembangkan API web atau layanan mikro di Java. Dengan Spark, Anda dapat memiliki REST API yang siap untuk melayani JSON dalam kurang dari sepuluh baris kode.

Kita akan memulai dengan cepat dengan contoh "Hello World", diikuti dengan REST API sederhana.

2. Ketergantungan Maven

2.1. Kerangka Spark

Sertakan dependensi Maven berikut di pom.xml Anda :

 com.sparkjava spark-core 2.5.4 

Anda dapat menemukan Spark versi terbaru di Maven Central.

2.2. Perpustakaan Gson

Di berbagai tempat dalam contoh ini, kami akan menggunakan pustaka Gson untuk operasi JSON. Untuk menyertakan Gson dalam proyek Anda, sertakan dependensi ini di pom.xml Anda :

 com.google.code.gson gson 2.8.0 

Anda dapat menemukan Gson versi terbaru di Maven Central.

3. Memulai Dengan Kerangka Spark

Mari kita lihat blok bangunan dasar dari aplikasi Spark dan mendemonstrasikan layanan web cepat.

3.1. Rute

Layanan web di Spark Java dibuat berdasarkan rute dan penangannya. Rute adalah elemen penting di Spark. Sesuai dokumentasi, setiap rute terdiri dari tiga bagian sederhana - kata kerja , jalur , dan panggilan balik .

  1. Kata kerja adalah metode yang sesuai dengan metode HTTP. Metode kata kerja meliputi: dapatkan, posting, letakkan, hapus, kepala, lacak, hubungkan, dan opsi
  2. The path (juga disebut pola rute) menentukan URI (s) rute harus mendengarkan dan memberikan respon untuk
  3. The callback adalah fungsi handler yang dipanggil untuk diberikan kata kerja dan jalan untuk menghasilkan dan kembali respon terhadap permintaan HTTP yang sesuai. Callback mengambil objek permintaan dan objek respons sebagai argumen

Di sini kami menunjukkan struktur dasar untuk rute yang menggunakan kata kerja get :

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

3.2. Halo Dunia API

Mari buat layanan web sederhana yang memiliki dua rute untuk permintaan GET dan mengembalikan pesan "Halo" sebagai tanggapan. Rute-rute ini menggunakan metode get , yang merupakan impor statis dari spark.Spark kelas :

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

Argumen pertama untuk metode get adalah jalur untuk rute tersebut. Rute pertama berisi jalur statis yang hanya mewakili satu URI ( “/ hello” ).

Jalur rute kedua ( "/ hello /: name" ) berisi placeholder untuk parameter "name" , seperti yang dilambangkan dengan mengawali parameter dengan titik dua (":"). Rute ini akan dipanggil sebagai tanggapan atas permintaan GET ke URI seperti “/ hello / Joe” dan “/ hello / Mary” .

Argumen kedua untuk metode get adalah ekspresi lambda yang memberikan rasa pemrograman fungsional ke kerangka kerja ini.

Ekspresi lambda memiliki permintaan dan respons sebagai argumen dan membantu mengembalikan respons. Kami akan menempatkan logika pengontrol kami dalam ekspresi lambda untuk rute REST API, seperti yang akan kita lihat nanti di tutorial ini.

3.3. Menguji Hello World API

Setelah menjalankan kelas HelloWorldService sebagai kelas Java normal, Anda akan dapat mengakses layanan pada port default 4567 menggunakan rute yang ditentukan dengan metode get di atas.

Mari kita lihat request dan respon untuk rute pertama:

Permintaan:

GET //localhost:4567/hello

Tanggapan:

Hello, world

Mari kita uji rute kedua, dengan meneruskan parameter name di jalurnya:

Permintaan:

GET //localhost:4567/hello/baeldung

Tanggapan:

Hello, baeldung

Lihat bagaimana penempatan teks "baeldung" di URI digunakan untuk mencocokkan pola rute "/ hello /: name" - menyebabkan fungsi penangan panggilan balik rute kedua dipanggil.

4. Mendesain Layanan RESTful

Di bagian ini, kami akan merancang layanan web REST sederhana untuk entitas Pengguna berikut :

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

4.1. Rute

Mari daftar rute yang membentuk API kita:

  • GET / pengguna - dapatkan daftar semua pengguna
  • GET / users /: id - dapatkan pengguna dengan id yang diberikan
  • POST / users /: id - tambahkan pengguna
  • PUT / users /: id - edit pengguna tertentu
  • OPSI / pengguna /: id - periksa apakah pengguna ada dengan id yang diberikan
  • HAPUS / pengguna /: id - menghapus pengguna tertentu

4.2. Layanan Pengguna

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

Dalam artikel ini, kami memiliki pengantar singkat tentang kerangka kerja Spark untuk pengembangan web yang cepat.

Kerangka kerja ini terutama dipromosikan untuk menghasilkan layanan mikro di Java. Pengembang Node.js dengan pengetahuan Java yang ingin memanfaatkan pustaka yang dibangun di pustaka JVM harus merasa betah menggunakan kerangka kerja ini.

Dan seperti biasa, Anda dapat menemukan semua sumber untuk tutorial ini di proyek Github.