JavaのPlayフレームワークを使ったREST API

1概要

このチュートリアルの目的は、Play Frameworkを調べ、Javaを使ってそれを使ってRESTサービスを構築する方法を学ぶことです。

生徒レコードを作成、取得、更新、削除するためのREST APIをまとめます。

そのようなアプリケーションでは、通常、学生の記録を保存するためのデータベースがあります。 PlayはHibernateや他の永続化フレームワークを使ったJPAのサポートと共に作り付けのH2データベースを持っています。

ただし、物事をシンプルに保ち、最も重要なものに焦点を合わせるために、一意のIDを持つ学生オブジェクトを格納するために単純なマップを使用します。

2 Play Frameworkのセットアップ

Playフレームワークのhttps://www.playframework.com/[official page]に行き、そのディストリビューションの最新版をダウンロードしましょう。このチュートリアルの時点では、最新のバージョンは 2.5 です。

これにより、 bin フォルダー内に activator というスクリプトが入った小さなスターターパッケージがダウンロードされます。このスクリプトは、さまざまなコンポーネントが必要になったときに、フレームワークの残りの部分を段階的にセットアップします。

簡単にするために、コンソールから実行するために activator PATH 変数に追加する必要があります。

Playのより穏やかな入門記事を入手するには、このリンクをたどることができます。

3新しいアプリケーションを作成する

play-Java テンプレートに基づいて student-api という新しいアプリケーションを作成しましょう。

activator new student-api play-Java

REST固有の概念についてのみ説明します。コードの残りの部分は一目瞭然です。

4プロジェクトの依存関係

Playでは、さまざまな方法でプロジェクトの依存関係として外部JARを追加できます。この場合は、プロジェクトのルートの lib フォルダーにJARを追加するという最も単純な方法を使用します。

私たちが使う唯一の外部依存関係は、彼のhttps://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22org.jsonに従うことによってMaven CentralからダウンロードできるJSONライブラリーです%22[リンク]

student-api/lib フォルダーがまだ存在しない場合は、作成してそこにjarファイルをドロップしてみましょう。ただし、これはPlay内からテストを実行するときにのみ使用されます。

RESTアプリケーションの場合と同様に、リクエストを送信してその結果についてアサーションを作成することで、サーバーを起動して他の環境からテストを実行することもできます。

物事を簡単にするために、これらのテストを実行するために任意のIDEを使用します。お望みなら、ブラウザからの GET リクエストをテストすることもできます。

5モデル

student-api/app/models に移動するか、まだ存在しない場合は作成します。それから生徒の情報を移動するためのJava Beanを作成しましょう。

public class Student {
    private String firstName;
    private String lastName;
    private int age;
    private int id;

   //standard constructors, getters and setters
}

CRUD操作を実行するためのヘルパーメソッドを使用して、生徒データ用の単純なデータストアを作成します。ここでは、 StudentStore.java です。

public class StudentStore {
    private Map<Integer, Student> students = new HashMap<>();

    public Student addStudent(Student student) {
        int id = students.size();
        student.setId(id);
        students.put(id, student);
        return student;
    }

    public Student getStudent(int id) {
        return students.get(id);
    }

    public Set<Student> getAllStudents() {
        return new HashSet<>(students.values());
    }

    public Student updateStudent(Student student) {
        int id = student.getId();
        if (students.containsKey(id)) {
            students.put(id, student);
            return student;
        }
        return null;
    }

    public boolean deleteStudent(int id) {
        return students.remove(id) != null;
    }
}

私たちは学生のデータをハッシュマップに保存しています。新しい生徒の記録ごとに整数のIDを作成し、それをキーとして使用します。

6. コントローラー

student-api/app/controllers に進んで、 StudentController.java という新しいコントローラーを作成しましょう。コードを少しずつ進めていきましょう。

Playはデータ処理を可能にするためにJacksonに同梱されているので、外部の依存関係なしに必要なJacksonクラスをインポートできます。

繰り返し操作を実行するためのユーティリティクラスを定義しましょう。この場合、HTTPレスポンスを構築します。それでは、 student-api/app/util パッケージを作成し、その中に Util.java を追加しましょう。

public class Util {
    public static ObjectNode createResponse(
      Object response, boolean ok) {

        ObjectNode result = Json.newObject();
        result.put("isSuccessfull", ok);
        if (response instanceof String) {
            result.put("body", (String) response);
        }
        else {
            result.put("body", (JsonNode) response);
        }

        return result;
    }
}

このメソッドでは、ブール値の isSuccessfull キーとレスポンスの body を使用して、標準のJSONレスポンスを作成します。

これでコントローラクラスのアクションを一通り進むことができます。

作成アクション:

public Result create() {
    JsonNode json = request().body().asJson();
    if (json == null){
        return badRequest(Util.createResponse(
          "Expecting Json data", false));
    }
    Student student = StudentStore.getInstance().addStudent(
      (Student) Json.fromJson(json, Student.class));
    JsonNode jsonObject = Json.toJson(student);
    return created(Util.createResponse(jsonObject, true));
}

スーパーコントローラクラスからの呼び出しを使用して、リクエストボディをJacksonの JsonNode クラスに取得します。本体が null の場合、どのようにしてユーティリティーメソッドを使用して応答を作成するのかに注意してください。

String または JsonNode と、状況を示すブール値のフラグを渡すことができます。

また、 Json.fromJson() を使用して、着信JSONオブジェクトを Student オブジェクトに変換し、その応答用にJSONに戻す方法にも注意してください。

最後に、私たちが慣れ親しんだ ok() の代わりに、 play.mvc.results パッケージの created helperメソッドを使っています。概念は他の行動を通して現れるでしょう。

アップデートアクション:

public Result update() {
    JsonNode json = request().body().asJson();
    if (json == null){
        return badRequest(Util.createResponse(
          "Expecting Json data", false));
    }
    Student student = StudentStore.getInstance().updateStudent(
      (Student) Json.fromJson(json, Student.class));
    if (student == null) {
        return notFound(Util.createResponse(
          "Student not found", false));
    }

    JsonNode jsonObject = Json.toJson(student);
    return ok(Util.createResponse(jsonObject, true));
}

取得アクション:

public Result retrieve(int id) {
    Student student = StudentStore.getInstance().getStudent(id);
    if (student == null) {
        return notFound(Util.createResponse(
          "Student with id:" + id + " not found", false));
    }
    JsonNode jsonObjects = Json.toJson(student);
    return ok(Util.createResponse(jsonObjects, true));
}

削除アクション:

public Result delete(int id) {
    boolean status = StudentStore.getInstance().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));
}

listStudentsアクション:

public Result listStudents() {
    Set<Student> result = StudentStore.getInstance().getAllStudents();
    ObjectMapper mapper = new ObjectMapper();

    JsonNode jsonData = mapper.convertValue(result, JsonNode.class);
    return ok(Util.createResponse(jsonData, true));
}

Jacksonの ObjectMapper を使用して Student オブジェクトのリストを JsonNode に変換する方法の違いに注目してください。

7. マッピング

コントローラアクションを設定したので、それらをマッピングし、 student-api/conf/routes ファイルを開き、次のルートを追加します。

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

静的リソースのダウンロードには、 /assets エンドポイントが常に存在している必要があります。

これで、学生用APIの構築は完了です。

8テスト中

http://localhost:9000/ にリクエストを送信したり、適切なコンテキストを追加したりしてテストを実行できます。ブラウザからベースパスを実行すると、次のように出力されます。

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

ご覧のとおり、まだレコードを追加していないため、本文は空です。 Javaクライアントを使用して、いくつかのテストを実行しましょう。繰り返しを避けるためにベースパスをグローバル化できます。

private static final String BASE__URL = "http://localhost:9000";

レコードを追加する:

 @Test
 public void whenCreatesRecord__thenCorrect() {
     Student student = new Student("jody", "west", 50);
     JSONObject obj = new JSONObject(makeRequest(
       BASE__URL, "POST", new JSONObject(student)));

     assertTrue(obj.getBoolean("isSuccessfull"));

     JSONObject body = obj.getJSONObject("body");

     assertEquals(student.getAge(), body.getInt("age"));
     assertEquals(student.getFirstName(), body.getString("firstName"));
     assertEquals(student.getLastName(), body.getString("lastName"));
    }

makeRequest メソッドの使用に注意してください。これは、HTTPリクエストを任意のURLに送信するための独自のヘルパーメソッドです。

public static String makeRequest(String myUrl,
  String httpMethod, JSONObject parameters) {
    URL url = null;
    url = new URL(myUrl);
    HttpURLConnection conn = null;
    conn = (HttpURLConnection) url.openConnection();
    conn.setDoInput(true);
    conn.setRequestProperty("Content-Type", "application/json");
    DataOutputStream dos = null;
    conn.setRequestMethod(httpMethod);

    if (Arrays.asList("POST", "PUT").contains(httpMethod)) {
        String params = parameters.toString();
        conn.setDoOutput(true);
        dos = new DataOutputStream(conn.getOutputStream());
        dos.writeBytes(params);
        dos.flush();
        dos.close();
    }

    int respCode = conn.getResponseCode();
    if (respCode != 200 && respCode != 201) {
        String error = inputStreamToString(conn.getErrorStream());
        return error;
    }
    String inputString = inputStreamToString(conn.getInputStream());

    return inputString;
}

そしてメソッド inputStreamToString :

public static String inputStreamToString(InputStream is) {
    BufferedReader br = null;
    StringBuilder sb = new StringBuilder();

    String line;
    br = new BufferedReader(new InputStreamReader(is));
    while ((line = br.readLine()) != null) {
        sb.append(line);
    }
    br.close();
    return sb.toString();
}

上記のテストを実行した後、ブラウザからhttp://localhost:9000 __をロードしてください。

{
    "isSuccessfull":true,
    "body": [{
        "firstName":"Barrack",
        "lastName":"Obama",
        "age":50,
        "id":1
    }]}

id 属性は、追加する新しいレコードごとに増えていきます。

レコードを削除する

@Test
public void whenDeletesCreatedRecord__thenCorrect() {
    Student student = new Student("Usain", "Bolt", 25);
    JSONObject ob1 = new JSONObject(
      makeRequest(BASE__URL, "POST",
        new JSONObject(student))).getJSONObject("body");
    int id = ob1.getInt("id");
    JSONObject obj1 = new JSONObject(
      makeRequest(BASE__URL + "/" + id, "POST", new JSONObject()));

    assertTrue(obj1.getBoolean("isSuccessfull"));

    makeRequest(BASE__URL + "/" + id, "DELETE", null);
    JSONObject obj2 = new JSONObject(
      makeRequest(BASE__URL + "/" + id, "POST", new JSONObject()));

    assertFalse(obj2.getBoolean("isSuccessfull"));
}

上記のテストでは、まず新しいレコードを作成し、その新しい id で正常に取得した後、削除しました。同じ id で再度検索しようとすると、操作は期待どおりに失敗します。

それでは、レコードを更新しましょう。

@Test
public void whenUpdatesCreatedRecord__thenCorrect() {
    Student student = new Student("Donald", "Trump", 50);
    JSONObject body1 = new JSONObject(
      makeRequest(BASE__URL, "POST",
        new JSONObject(student))).getJSONObject("body");

    assertEquals(student.getAge(), body1.getInt("age"));

    int newAge = 60;
    body1.put("age", newAge);
    JSONObject body2 = new JSONObject(
      makeRequest(BASE__URL, "PUT", body1)).getJSONObject("body");

    assertFalse(student.getAge() == body2.getInt("age"));
    assertTrue(newAge == body2.getInt("age"));
}

上記のテストは、レコードを更新した後の age フィールドの値の変化を示しています。

すべての記録を取得する:

 Student student1 = new Student("jane", "daisy", 50);
    Student student2 = new Student("john", "daniel", 60);
    Student student3 = new Student("don", "mason", 55);
    Student student4 = new Student("scarlet", "ohara", 90);

    makeRequest(BASE__URL, "POST", new JSONObject(student1));
    makeRequest(BASE__URL, "POST", new JSONObject(student2));
    makeRequest(BASE__URL, "POST", new JSONObject(student3));
    makeRequest(BASE__URL, "POST", new JSONObject(student4));

    JSONObject objects = new JSONObject(makeRequest(BASE__URL, "GET", null));
    assertTrue(objects.getBoolean("isSuccessfull"));
    JSONArray array = objects.getJSONArray("body");
    assertTrue(array.length() >= 4);

上記のテストで、 listStudents コントローラーアクションの適切な機能を確認しています。

9結論

この記事では、Play Frameworkを使用して本格的なREST APIを構築する方法を説明しました。

この記事の完全なソースコードと例はhttps://github.com/eugenp/tutorials/tree/master/play-framework[GitHubプロジェクト]にあります。

前の投稿:Spring Security - ホワイトリストのIP範囲
次の投稿:Java - InputStreamをファイルに書き込む