Membangun API Dengan Kerangka Spark Java

1. Pengenalan

Dalam artikel ini, kita akan mendapat pengenalan ringkas mengenai kerangka Spark. Spark framework adalah kerangka web pengembangan pesat yang diilhami oleh kerangka Sinatra untuk Ruby dan dibangun di sekitar falsafah Java 8 Lambda Expression, menjadikannya kurang verbose daripada kebanyakan aplikasi yang ditulis dalam kerangka Java lainnya.

Ini adalah pilihan yang baik jika anda ingin memiliki pengalaman seperti Node.js ketika mengembangkan API web atau perkhidmatan mikro di Java. Dengan Spark, anda dapat menyediakan REST API yang siap disajikan JSON dalam masa kurang dari sepuluh baris kod.

Kami akan memulakan dengan cepat dengan contoh "Hello World", diikuti dengan REST API yang ringkas.

2. Pergantungan Maven

2.1. Kerangka Spark

Sertakan kebergantungan Maven berikut di pom.xml anda :

 com.sparkjava spark-core 2.5.4 

Anda boleh mendapatkan versi terbaru Spark di Maven Central.

2.2. Perpustakaan Gson

Di pelbagai tempat dalam contoh, kami akan menggunakan perpustakaan Gson untuk operasi JSON. Untuk memasukkan Gson dalam projek anda, sertakan kebergantungan ini dalam pom.xml anda :

 com.google.code.gson gson 2.8.0 

Anda boleh mendapatkan versi terbaru Gson di Maven Central.

3. Bermula Dengan Kerangka Spark

Mari lihat blok asas aplikasi Spark dan tunjukkan perkhidmatan web yang cepat.

3.1. Laluan

Perkhidmatan web di Spark Java dibangun berdasarkan rute dan pengendali mereka. Laluan adalah elemen penting dalam Spark. Sesuai dokumentasi, setiap rute terdiri dari tiga bagian sederhana - kata kerja , jalan , dan panggilan balik .

  1. Kata kerja adalah kaedah yang sesuai dengan kaedah HTTP. Kaedah kata kerja meliputi: get, post, let, delete, head, trace, connect, dan pilihan
  2. Yang jalan (juga dikenali sebagai corak laluan) menentukan URI (s) laluan yang harus mendengar dan memberi maklum balas untuk
  3. The panggil balik adalah fungsi pengendali yang berdoa untuk diberi kata kerja dan laluan dalam usaha untuk menjana dan mengembalikan balasan kepada permintaan HTTP yang sepadan. Panggilan balik mengambil objek permintaan dan objek tindak balas sebagai argumen

Di sini kami menunjukkan struktur asas untuk laluan yang menggunakan kata kerja get :

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

3.2. Hello World API

Mari buat perkhidmatan web mudah yang mempunyai dua laluan untuk permintaan GET dan mengembalikan mesej "Hello" sebagai respons. Laluan ini menggunakan kaedah get , yang merupakan import statik dari percikan 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 kaedah get adalah jalan untuk laluan. Laluan pertama mengandungi jalan statik yang hanya mewakili URI tunggal ( “/ hello” ).

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

Argumen kedua untuk kaedah get adalah ungkapan lambda yang memberikan rasa pengaturcaraan fungsional untuk kerangka ini.

Ungkapan lambda mempunyai permintaan dan respons sebagai hujah dan membantu mengembalikan respons. Kami akan memasukkan logik pengawal kami dalam ungkapan lambda untuk laluan REST API, seperti yang akan kita lihat kemudian dalam tutorial ini.

3.3. Menguji API Hello World

Setelah menjalankan kelas HelloWorldService sebagai kelas Java biasa, anda akan dapat mengakses perkhidmatan di port lalai 4567 menggunakan laluan yang ditentukan dengan kaedah get di atas.

Mari lihat permintaan dan respons untuk laluan pertama:

Permintaan:

GET //localhost:4567/hello

Jawapan:

Hello, world

Mari kita menguji laluan kedua, melewati parameter nama di jalannya:

Permintaan:

GET //localhost:4567/hello/baeldung

Jawapan:

Hello, baeldung

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

4. Merancang Perkhidmatan TERBAIK

Di bahagian ini, kami akan merancang perkhidmatan web REST sederhana untuk entiti Pengguna berikut :

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

4.1. Laluan

Mari senaraikan laluan yang membentuk API kami:

  • DAPATKAN / pengguna - dapatkan senarai semua pengguna
  • GET / users /: id - dapatkan pengguna dengan id yang diberikan
  • POST / users /: id - tambahkan pengguna
  • PUT / users /: id - edit pengguna tertentu
  • PILIHAN / pengguna /: id - periksa sama ada pengguna wujud dengan id yang diberikan
  • HAPUS / pengguna /: id - hapus pengguna tertentu

4.2. Perkhidmatan 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 telah memperkenalkan pengenalan ringkas mengenai kerangka Spark untuk pengembangan web yang cepat.

Kerangka ini terutama dipromosikan untuk menghasilkan perkhidmatan mikro di Java. Pembangun Node.js dengan pengetahuan Java yang ingin memanfaatkan perpustakaan yang dibina di perpustakaan JVM semestinya merasa selesa di rumah menggunakan kerangka ini.

Dan seperti biasa, anda boleh menemui semua sumber tutorial ini dalam projek Github.