JavaのPlay Frameworkを使用したREST API
1. 概要
このチュートリアルの目的は、Play Frameworkを調べ、Javaを使用してRESTサービスを構築する方法を学ぶことです。
学生の記録を作成、取得、更新、削除するためのRESTAPIをまとめます。
このようなアプリケーションでは、通常、学生の記録を保存するデータベースがあります。 PlayフレームワークにはH2データベースが組み込まれており、Hibernateおよびその他の永続化フレームワークを使用したJPAのサポートがあります。
ただし、物事をシンプルに保ち、最も重要なものに集中するために、シンプルなマップを使用して、一意のIDを持つ学生オブジェクトを保存します。
2. 新しいアプリケーションを作成する
Introduction to the Play Frameworkで説明されているようにPlay Frameworkをインストールしたら、アプリケーションを作成する準備が整います。
sbtコマンドを使用して、play-java-seedを使用してstudent-apiという新しいアプリケーションを作成しましょう。
sbt new playframework/play-java-seed.g8
3. モデル
アプリケーションのスキャフォールディングを配置したら、student-api/app/modelsに移動して、学生情報を処理するためのJavaBeanを作成しましょう。
public class Student {
private String firstName;
private String lastName;
private int age;
private int id;
// standard constructors, getters and setters
}
次に、学生データ用のHashMap –に裏打ちされた、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. コントローラー
student-api/app/controllersに移動して、StudentController.javaという新しいコントローラーを作成しましょう。 コードを段階的に進めていきます。
まず、configure an *HttpExecutionContext*.を実行する必要があります。非同期の非ブロッキングコードを使用してアクションを実装します。 これは、アクションメソッドがResultだけでなくCompletionStage<Result> を返すことを意味します。 これには、ブロックせずに長時間実行されるタスクを作成できるという利点があります。
Play Frameworkコントローラーで非同期プログラミングを処理する場合の注意点が1つだけあります。HttpExecutionContext.を指定する必要があります。HTTP実行コンテキストを指定しないと、「HTTPコンテキストがありません」という悪名高いエラーが発生します。アクションメソッドを呼び出すときに「ここから利用可能」。
それを注入しましょう:
private HttpExecutionContext ec;
private StudentStore studentStore;
@Inject
public StudentController(HttpExecutionContext ec, StudentStore studentStore) {
this.studentStore = studentStore;
this.ec = ec;
}
また、StudentStore を追加し、@Inject annotationを使用してコントローラーのコンストラクターに両方のフィールドを挿入したことに注意してください。 これが完了したら、アクションメソッドの実装に進むことができます。
Play ships with Jackson to allow for data processing –外部依存関係なしで必要なJacksonクラスをインポートできることに注意してください。
繰り返し操作を実行するユーティリティクラスを定義しましょう。 この場合、HTTP応答を構築します。
それでは、student-api/app/utilsパッケージを作成し、それにUtil.javaを追加しましょう。
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;
}
}
このメソッドを使用して、ブール値のisSuccessfulキーと応答本文を使用して標準のJSON応答を作成します。
これで、コントローラークラスのアクションをステップ実行できます。
4.1. createアクション
POST actionとしてマップされたこのメソッドは、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());
}
注入されたHttp.Request classからの呼び出しを使用して、リクエストの本文をJacksonのJsonNodeクラスに取り込みます。 本文がnullの場合、ユーティリティメソッドを使用して応答を作成する方法に注目してください。
また、CompletionStage<Result>を返します。これにより、CompletedFuture.supplyAsync メソッドを使用してノンブロッキングコードを記述できます。
ステータスを示すbooleanフラグとともに、任意のStringまたはJsonNodeを渡すことができます。
また、Json.fromJson()を使用して着信JSONオブジェクトをStudentオブジェクトに変換し、応答のためにJSONに戻す方法にも注目してください。
最後に、これまで使用していたok()の代わりに、play.mvc.resultsパッケージのcreatedヘルパーメソッドを使用しています。 アイデアは、特定のコンテキスト内で実行されているアクションの正しいHTTPステータスを提供するメソッドを使用することです。 たとえば、HTTP OK 200ステータスの場合はok()、HTTP CREATED 201の場合はcreated()が上記の結果ステータスです。 この概念は、残りのアクションの全体にわたって登場します。
4.2. updateアクション
http://localhost:9000/へのPUT リクエストはStudentController.updateメソッドにヒットし、StudentStoreのupdateStudent メソッドを呼び出すことで学生情報を更新します。
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. retrieveアクション
学生を取得するには、GET リクエストのパスパラメータとして学生のIDをhttp://localhost:9000/:idに渡します。 これは、retrieve actionにヒットします。
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. deleteアクション
delete actionはhttp://localhost:9000/:idにマップされます。 削除するレコードを識別するために、idを指定します。
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. listStudentsアクション
最後に、listStudentsアクションは、これまでに保存されたすべての生徒のリストを返します。 GETリクエストとしてhttp://localhost:9000/にマッピングされます。
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. マッピング
コントローラアクションを設定したら、ファイルstudent-api/conf/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)
静的リソースをダウンロードするには、/assetsエンドポイントが常に存在している必要があります。
この後、StudentAPIの構築が完了しました。
ルートマッピングの定義の詳細については、Routing in Play Applicationsチュートリアルにアクセスしてください。
6. テスト
これで、http://localhost:9000/にリクエストを送信し、適切なコンテキストを追加することで、APIでテストを実行できます。 ブラウザからベースパスを実行すると、次の出力が表示されます。
{
"isSuccessful":true,
"body":[]
}
ご覧のとおり、まだレコードを追加していないため、本文は空です。 curlを使用して、いくつかのテストを実行しましょう(または、PostmanのようなRESTクライアントを使用できます)。
ターミナルウィンドウを開いて、add a studentに対してcurlコマンドを実行してみましょう。
curl -X POST -H "Content-Type: application/json" \
-d '{"firstName":"John","lastName":"example","age": 18}' \
http://localhost:9000/
これにより、新しく作成された学生が返されます。
{
"isSuccessful":true,
"body":{
"firstName":"John",
"lastName":"example",
"age":18,
"id":0
}
}
上記のテストを実行した後、ブラウザからhttp://localhost:9000をロードすると、次のようになります。
{
"isSuccessful":true,
"body":[
{
"firstName":"John",
"lastName":"example",
"age":18,
"id":0
}
]
}
id属性は、新しいレコードを追加するたびにインクリメントされます。
delete a recordに、DELETEリクエストを送信します。
curl -X DELETE http://localhost:9000/0
{
"isSuccessful":true,
"body":"Student with id:0 deleted"
}
上記のテストでは、最初のテストで作成されたレコードを削除します。次に、create it again so that we can test the update methodを実行します。
curl -X POST -H "Content-Type: application/json" \
-d '{"firstName":"John","lastName":"example","age": 18}' \
http://localhost:9000/
{
"isSuccessful":true,
"body":{
"firstName":"John",
"lastName":"example",
"age":18,
"id":0
}
}
名を「Andrew」に設定し、年齢を30に設定して、update the recordにしましょう。
curl -X PUT -H "Content-Type: application/json" \
-d '{"firstName":"Andrew","lastName":"example","age": 30,"id":0}' \
http://localhost:9000/
{
"isSuccessful":true,
"body":{
"firstName":"Andrew",
"lastName":"example",
"age":30,
"id":0
}
}
上記のテストは、レコードを更新した後のfirstName フィールドとageフィールドの値の変化を示しています。
追加のダミーレコードをいくつか作成しましょう。JohnDoeとSamの例の2つを追加します。
curl -X POST -H "Content-Type: application/json" \
-d '{"firstName":"John","lastName":"Doe","age": 18}' \
http://localhost:9000/
curl -X POST -H "Content-Type: application/json" \
-d '{"firstName":"Sam","lastName":"example","age": 25}' \
http://localhost:9000/
それでは、すべてのレコードを取得しましょう。
curl -X GET http://localhost:9000/
{
"isSuccessful":true,
"body":[
{
"firstName":"Andrew",
"lastName":"example",
"age":30,
"id":0
},
{
"firstName":"John",
"lastName":"Doe",
"age":18,
"id":1
},
{
"firstName":"Sam",
"lastName":"example",
"age":25,
"id":2
}
]
}
上記のテストにより、listStudentsコントローラーアクションが適切に機能していることを確認しています。
7. 結論
この記事では、PlayFrameworkを使用して本格的なRESTAPIを構築する方法を示しました。
いつものように、このチュートリアルのソースコードはover on GitHubで入手できます。