API REST avec Play Framework en Java

1. Vue d'ensemble

Le but de ce didacticiel est d'explorer le Framework Play et d'apprendre à créer des services REST avec lui à l'aide de Java.

Nous mettrons en place une API REST pour créer, récupérer, mettre à jour et supprimer les enregistrements des étudiants.

Dans de telles applications, nous aurions normalement une base de données pour stocker les dossiers des étudiants. Play Framework dispose d'une base de données H2 intégrée, ainsi que de la prise en charge de JPA avec Hibernate et d'autres frameworks de persistance.

Cependant, pour garder les choses simples et nous concentrer sur les choses les plus importantes, nous utiliserons une carte simple pour stocker les objets des élèves avec des identifiants uniques.

2. Créez une nouvelle application

Une fois que nous avons installé Play Framework comme décrit dans notre Introduction à Play Framework, nous sommes prêts à créer notre application.

Utilisons la commande sbt pour créer une nouvelle application appelée student-api en utilisant play-java-seed :

sbt new playframework/play-java-seed.g8

3. Modèles

Avec notre échafaudage d'application en place, naviguons vers student-api / app / models et créons un bean Java pour gérer les informations des étudiants:

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

Nous allons maintenant créer un magasin de données simple - soutenu par un HashMap - pour les données des étudiants, avec des méthodes d'assistance pour effectuer des opérations 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. Contrôleurs

Passons à student-api / app / controllers et créons un nouveau contrôleur appelé StudentController.java . Nous allons parcourir le code de manière incrémentielle.

Tout d'abord, nous devons configurer un HttpExecutionContext . Nous implémenterons nos actions en utilisant du code asynchrone et non bloquant. Cela signifie que nos méthodes d'action renverront CompletionStage au lieu de simplement Result . Cela a l'avantage de nous permettre d'écrire des tâches de longue durée sans blocage.

Il y a juste une mise en garde concernant la programmation asynchrone dans un contrôleur Play Framework: nous devons fournir un HttpExecutionContext. Si nous ne fournissons pas le contexte d'exécution HTTP, nous obtiendrons la tristement célèbre erreur «Il n'y a pas de contexte HTTP disponible à partir d'ici» lors de l'appel de la méthode d'action.

Injectons-le:

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

Notez que nous avons également ajouté le StudentStore et injecté les deux champs dans le constructeur du contrôleur à l'aide de l' annotation @Inject . Cela fait, nous pouvons maintenant procéder à la mise en œuvre des méthodes d'action.

Notez que Play est livré avec Jackson pour permettre le traitement des données - nous pouvons donc importer toutes les classes Jackson dont nous avons besoin sans dépendances externes.

Définissons une classe utilitaire pour effectuer des opérations répétitives. Dans ce cas, créer des réponses HTTP.

Créons étudiant-api / app / utils package et ajouter Util.java en elle:

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

Avec cette méthode, nous allons créer des réponses JSON standard avec une clé booléenne isSuccessful et le corps de la réponse.

Nous pouvons maintenant parcourir les actions de la classe de contrôleur.

4.1. L' action de création

Mappée en tant qu'action POST , cette méthode gère la création de l' objet 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()); }

Nous utilisons un appel de la classe Http.Request injectée pour obtenir le corps de la requête dans la classe JsonNode de Jackson . Notez comment nous utilisons la méthode utilitaire pour créer une réponse si le corps est nul .

Nous renvoyons également un CompletionStage , qui nous permet d'écrire du code non bloquant à l'aide de la méthode CompletedFuture.supplyAsync .

Nous pouvons lui passer n'importe quel String ou JsonNode , avec un drapeau booléen pour indiquer l'état.

Notez également comment nous utilisons Json.fromJson () pour convertir l'objet JSON entrant en objet Student et le retourner en JSON pour la réponse.

Enfin, au lieu de ok () auquel nous sommes habitués, nous utilisons la méthode d'aide créée à partir du package play.mvc.results . L'idée est d'utiliser une méthode qui donne le statut HTTP correct pour l'action en cours d'exécution dans un contexte particulier. Par exemple, ok () pour l'état HTTP OK 200 et created () lorsque HTTP CREATED 201 est l'état du résultat utilisé ci-dessus. Ce concept reviendra tout au long du reste des actions.

4.2. L' action de mise à jour

Une requête PUT à // localhost: 9000 / atteint le StudentController. update , qui met à jour les informations de l'étudiant en appelant la méthode updateStudent de 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. L' action de récupération

Pour récupérer un étudiant, nous transmettons l'id de l'étudiant comme paramètre de chemin dans une requête GET à // localhost: 9000 /: id . Cela frappera l' action de récupération :

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. L' action de suppression

L' action de suppression est mappée sur // localhost: 9000 /: id . Nous fournissons l' identifiant pour identifier l'enregistrement à supprimer:

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

Dans cet article, nous avons montré comment créer une API REST complète à l'aide de Play Framework.

Comme d'habitude, le code source de ce tutoriel est disponible à l'adresse over sur GitHub.