REST API dengan Play Framework di Java

1. Ikhtisar

Tujuan dari tutorial ini adalah untuk menjelajahi Kerangka Play dan mempelajari cara membangun layanan REST dengannya menggunakan Java.

Kami akan mengumpulkan REST API untuk membuat, mengambil, memperbarui, dan menghapus catatan siswa.

Dalam aplikasi seperti itu, kami biasanya memiliki database untuk menyimpan catatan siswa. Kerangka kerja Play memiliki basis data H2 bawaan, bersama dengan dukungan untuk JPA dengan Hibernate dan kerangka kerja ketekunan lainnya.

Namun, untuk membuat semuanya tetap sederhana dan fokus pada hal yang paling penting, kita akan menggunakan peta sederhana untuk menyimpan objek siswa dengan ID unik.

2. Buat Aplikasi Baru

Setelah kami menginstal Kerangka Play seperti yang dijelaskan dalam Pengantar Kerangka Play kami, kami siap untuk membuat aplikasi kami.

Mari gunakan perintah sbt untuk membuat aplikasi baru bernama student-api menggunakan play-java-seed :

sbt new playframework/play-java-seed.g8

3. Model

Dengan perancah aplikasi kita di tempatnya, mari arahkan ke student-api / app / models dan buat kacang Java untuk menangani informasi siswa:

public class Student { private String firstName; private String lastName; private int age; private int id; // standard constructors, getters and setters }

Sekarang kita akan membuat penyimpanan data sederhana - didukung oleh HashMap - untuk data siswa, dengan metode pembantu untuk melakukan operasi CRUD:

public class StudentStore { private Map students = new HashMap(); public Optional addStudent(Student student) { int id = students.size(); student.setId(id); students.put(id, student); return Optional.ofNullable(student); } public Optional getStudent(int id) { return Optional.ofNullable(students.get(id)); } public Set getAllStudents() { return new HashSet(students.values()); } public Optional updateStudent(Student student) { int id = student.getId(); if (students.containsKey(id)) { students.put(id, student); return Optional.ofNullable(student); } return null; } public boolean deleteStudent(int id) { return students.remove(id) != null; } }

4. Pengontrol

Mari menuju ke student-api / app / controllers dan buat controller baru bernama StudentController.java . Kami akan menelusuri kode secara bertahap.

Pertama, kita perlu mengkonfigurasi HttpExecutionContext . Kami akan menerapkan tindakan kami menggunakan kode asinkron, non-pemblokiran. Ini berarti bahwa metode tindakan kami akan mengembalikan CompletionStage, bukan hanya Hasil . Ini memiliki keuntungan memungkinkan kita untuk menulis tugas yang berjalan lama tanpa pemblokiran.

Hanya ada satu peringatan saat berurusan dengan pemrograman asinkron di pengontrol Play Framework: kita harus menyediakan HttpExecutionContext. Jika kita tidak menyediakan konteks eksekusi HTTP, kita akan mendapatkan kesalahan terkenal "Tidak ada Konteks HTTP yang tersedia dari sini" saat memanggil metode aksi.

Mari kita menyuntikkannya:

private HttpExecutionContext ec; private StudentStore studentStore; @Inject public StudentController(HttpExecutionContext ec, StudentStore studentStore) { this.studentStore = studentStore; this.ec = ec; }

Perhatikan bahwa kami juga telah menambahkan StudentStore dan memasukkan kedua bidang dalam konstruktor pengontrol menggunakan anotasi @Inject . Setelah melakukan ini, sekarang kita dapat melanjutkan dengan penerapan metode tindakan.

Perhatikan bahwa Play dikirimkan bersama Jackson untuk memungkinkan pemrosesan data - sehingga kami dapat mengimpor kelas Jackson apa pun yang kami butuhkan tanpa ketergantungan eksternal.

Mari kita definisikan kelas utilitas untuk melakukan operasi berulang. Dalam kasus ini, membuat respons HTTP.

Jadi mari buat paket student-api / app / utils dan tambahkan Util.java di dalamnya:

public class Util { public static ObjectNode createResponse(Object response, boolean ok) { ObjectNode result = Json.newObject(); result.put("isSuccessful", ok); if (response instanceof String) { result.put("body", (String) response); } else { result.putPOJO("body", response); } return result; } }

Dengan metode ini, kita akan membuat respons JSON standar dengan kunci isSuccessful boolean dan isi respons.

Sekarang kita dapat melangkah melalui tindakan kelas pengontrol.

4.1. The menciptakan Aksi

Dipetakan sebagai tindakan POST , metode ini menangani pembuatan objek Student :

public CompletionStage create(Http.Request request) { JsonNode json = request.body().asJson(); return supplyAsync(() -> { if (json == null) { return badRequest(Util.createResponse("Expecting Json data", false)); } Optional studentOptional = studentStore.addStudent(Json.fromJson(json, Student.class)); return studentOptional.map(student -> { JsonNode jsonObject = Json.toJson(student); return created(Util.createResponse(jsonObject, true)); }).orElse(internalServerError(Util.createResponse("Could not create data.", false))); }, ec.current()); }

Kami menggunakan panggilan dari kelas Http.Request yang diinjeksi untuk memasukkan isi permintaan ke dalam kelas JsonNode Jackson . Perhatikan bagaimana kami menggunakan metode utilitas untuk membuat respons jika body-nya null .

Kami juga mengembalikan CompletionStage , yang memungkinkan kami menulis kode non-pemblokiran menggunakan metode CompletedFuture.supplyAsync .

Kita bisa memberikannya String atau JsonNode apa saja , bersama dengan flag boolean untuk menunjukkan status.

Perhatikan juga bagaimana kita menggunakan Json.fromJson () untuk mengubah objek JSON yang masuk menjadi objek Student dan kembali ke JSON untuk responnya.

Akhirnya, alih-alih ok () yang biasa kita gunakan, kita menggunakan metode helper yang dibuat dari paket play.mvc.results . Idenya adalah menggunakan metode yang memberikan status HTTP yang benar untuk tindakan yang dilakukan dalam konteks tertentu. Misalnya, ok () untuk status HTTP OK 200, dan dibuat () saat HTTP CREATED 201 adalah status hasil seperti yang digunakan di atas. Konsep ini akan muncul di seluruh tindakan lainnya.

4.2. The pembaruan Aksi

Sebuah PUT permintaan untuk // localhost: 9000 / hits StudentController. metode update , yang mengupdate informasi siswa dengan memanggil metode updateStudent dari StudentStore :

public CompletionStage update(Http.Request request) { JsonNode json = request.body().asJson(); return supplyAsync(() -> { if (json == null) { return badRequest(Util.createResponse("Expecting Json data", false)); } Optional studentOptional = studentStore.updateStudent(Json.fromJson(json, Student.class)); return studentOptional.map(student -> { if (student == null) { return notFound(Util.createResponse("Student not found", false)); } JsonNode jsonObject = Json.toJson(student); return ok(Util.createResponse(jsonObject, true)); }).orElse(internalServerError(Util.createResponse("Could not create data.", false))); }, ec.current()); }

4.3. The mengambil Aksi

Untuk mengambil siswa, kita mengirimkan id siswa sebagai parameter jalur dalam permintaan GET ke // localhost: 9000 /: id . Ini akan menekan tindakan ambil :

public CompletionStage retrieve(int id) { return supplyAsync(() -> { final Optional studentOptional = studentStore.getStudent(id); return studentOptional.map(student -> { JsonNode jsonObjects = Json.toJson(student); return ok(Util.createResponse(jsonObjects, true)); }).orElse(notFound(Util.createResponse("Student with id:" + id + " not found", false))); }, ec.current()); }

4.4. The delete Aksi

Tindakan hapus dipetakan ke // localhost: 9000 /: id . Kami menyediakan id untuk mengidentifikasi record mana yang akan dihapus:

public CompletionStage delete(int id) { return supplyAsync(() -> { boolean status = studentStore.deleteStudent(id); if (!status) { return notFound(Util.createResponse("Student with id:" + id + " not found", false)); } return ok(Util.createResponse("Student with id:" + id + " deleted", true)); }, ec.current()); }

4.5. The listStudents Action

Finally, the listStudents action returns a list of all the students that have been stored so far. It's mapped to //localhost:9000/ as a GET request:

public CompletionStage listStudents() { return supplyAsync(() -> { Set result = studentStore.getAllStudents(); ObjectMapper mapper = new ObjectMapper(); JsonNode jsonData = mapper.convertValue(result, JsonNode.class); return ok(Util.createResponse(jsonData, true)); }, ec.current()); }

5. Mappings

Having set up our controller actions, we can now map them by opening the file student-api/conf/routes and adding these routes:

GET / controllers.StudentController.listStudents() GET /:id controllers.StudentController.retrieve(id:Int) POST / controllers.StudentController.create(request: Request) PUT / controllers.StudentController.update(request: Request) DELETE /:id controllers.StudentController.delete(id:Int) GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)

The /assets endpoint must always be present for downloading static resources.

After this, we're done with building the Student API.

To learn more about defining route mappings, visit our Routing in Play Applications tutorial.

6. Testing

We can now run tests on our API by sending requests to //localhost:9000/ and adding the appropriate context. Running the base path from the browser should output:

{ "isSuccessful":true, "body":[] }

As we can see, the body is empty since we haven't added any records yet. Using curl, let's run some tests (alternatively, we can use a REST client like Postman).

Let's open up a terminal window and execute the curl command to add a student:

curl -X POST -H "Content-Type: application/json" \ -d '{"firstName":"John","lastName":"Baeldung","age": 18}' \ //localhost:9000/

This will return the newly created student:

{ "isSuccessful":true, "body":{ "firstName":"John", "lastName":"Baeldung", "age":18, "id":0 } }

After running the above test, loading //localhost:9000 from the browser should now give us:

{ "isSuccessful":true, "body":[ { "firstName":"John", "lastName":"Baeldung", "age":18, "id":0 } ] } 

The id attribute will be incremented for every new record we add.

To delete a record we send a DELETE request:

curl -X DELETE //localhost:9000/0 { "isSuccessful":true, "body":"Student with id:0 deleted" } 

In the above test, we delete the record created in the first test, now let's create it again so that we can test the update method:

curl -X POST -H "Content-Type: application/json" \ -d '{"firstName":"John","lastName":"Baeldung","age": 18}' \ //localhost:9000/ { "isSuccessful":true, "body":{ "firstName":"John", "lastName":"Baeldung", "age":18, "id":0 } }

Let's now update the record by setting the first name to “Andrew” and age to 30:

curl -X PUT -H "Content-Type: application/json" \ -d '{"firstName":"Andrew","lastName":"Baeldung","age": 30,"id":0}' \ //localhost:9000/ { "isSuccessful":true, "body":{ "firstName":"Andrew", "lastName":"Baeldung", "age":30, "id":0 } }

The above test demonstrates the change in the value of the firstName and age fields after updating the record.

Let's create some extra dummy records, we'll add two: John Doe and Sam Baeldung:

curl -X POST -H "Content-Type: application/json" \ -d '{"firstName":"John","lastName":"Doe","age": 18}' \ //localhost:9000/
curl -X POST -H "Content-Type: application/json" \ -d '{"firstName":"Sam","lastName":"Baeldung","age": 25}' \ //localhost:9000/

Now, let's get all the records:

curl -X GET //localhost:9000/ { "isSuccessful":true, "body":[ { "firstName":"Andrew", "lastName":"Baeldung", "age":30, "id":0 }, { "firstName":"John", "lastName":"Doe", "age":18, "id":1 }, { "firstName":"Sam", "lastName":"Baeldung", "age":25, "id":2 } ] }

With the above test, we are ascertaining the proper functioning of the listStudents controller action.

7. Conclusion

Di artikel ini, kami telah menunjukkan cara membuat REST API lengkap menggunakan Play Framework.

Seperti biasa, kode sumber untuk tutorial ini tersedia di GitHub.