Spark Frameworkの使い方:Getting Start編はこちら
今回はSpark Frameworkを使ったRESTアプリの作成について解説します。
今回使用するライブラリ
- spark-core 2.7.1
- spark-debug-tools 0.5
- gson 2.8.2
gsonを依存関係に追加します。
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
Spark FrameworkにおけるRESTアプリ
Getting Start編でやったように、Spark Frameworkではパスにマッピングしたハンドラでリクエストを処理し、戻り値としてレスポンスボディを返却します。
import static spark.Spark.get;
public class Application {
public static void main(String[] args) {
get("/", (req, res) -> "hello world!");
}
}
マッピングメソッドはHTTP Methodに対応して、get
・post
・put
・delete
が用意されています。
get("/", (req, res) -> { ... });
post("/", (req, res) -> { ... });
put("/", (req, res) -> { ... });
delete("/", (req, res) -> { ... });
また、各マッピングメソッドには、レスポンスの内容によって異なるRoute
インターフェイスが用意されています。
get(String, Route);
get(String, Route, ResponseTransformer);
get(String, TemplateViewRoute, TemplateEngine);
RESTアプリではレスポンスとしてJSONを返却するため、get(String, Route, ResponseTransformer);
を利用します。
ResponseTransformer
はハンドラが返却した戻り値を変換するためのインターフェイスで、Route
と同様に@FunctionalInterface
で定義されています。
RESTアプリを実装する
前提となる実装
今回はTODOリストを管理するRESTアプリを作成します。
TODOは以下のモデルで登録します。
- Todo.java
@data
public class Todo {
private String id;
private String title;
private LocalDateTime created;
private boolean finished;
}
モデルを操作・永続化するリポジトリは以下のとおりです。
なお、リポジトリの実装は主眼ではないので省略します。
- TodoRepository.java
public class TodoRepository {
public List<Todo> findAll() { ... }
public Optional<Todo> findOne(String id) { ... }
public void create(String title) { ... }
public void finish(String id) { ... }
public void remove(String id) { ... }
}
実装
それでは、RESTアプリを実装していきます。
ここでは、ハンドラの処理が複雑化することを考慮して、ハンドラをApplication.javaから分離してコントローラクラスを作成します。
- TodoController.java
public class TodoController {
private static final TodoRepository repository = new TodoRepository();
private static final Gson gson = new Gson();
// (1)
public static final Route findAll = (req, res) -> {
return repository.findAll(); // (2)
};
public static final Route findOne = (req, res) -> {
return repository.findOne(req.params("id")); // (3)
};
public static final Route create = (req, res) -> {
Todo todo = gson.fromJson(req.body(), Todo.class); // (4)
repository.create(todo.getTitle());
return Collections.singletonMap("message", "created.");
};
public static final Route finish = (req, res) -> {
repository.finish(req.params("id"));
return Collections.singletonMap("message", "finished.");
};
public static final Route remove = (req, res) -> {
repository.remove(req.params("id"));
return Collections.singletonMap("message", "removed.");
};
}
no. | description |
---|---|
(1) | 各ハンドラはRoute インターフェイスを実装します。@FunctionalInterface なので分かりづらいかもしれませんが、匿名クラスのオブジェクトを生成してクラスフィールドに格納しています。 |
(2) | 戻り値として、JSONに変換したいオブジェクトを返却します。JSONへの変換は前述のとおりResponseTransformer が実施します。 |
(3) |
Request#params メソッドでパス変数を取得できます。メソッド名から勘違いしそうですが、取得できるのはリクエストパラメータではなくパス変数(パスの一部)です。 |
(4) | JSONで送信されたリクエストボディはGson#formJson メソッドでTodo オブジェクトに復元します。要はSpark側でリクエストボディを復元する仕組みは用意されていないので、自分でやる必要があるということです。 |
ハンドラを格納するのはクラスフィールドである必要はありません。DIすることを考慮すると、インスタンスフィールドにするほうが適切かもしれません。
次に、ハンドラの戻り値をレスポンスに変換するResponseTransformer
を作成します。
ここではgsonを利用してオブジェクトをJSONに変換します。
- JsonResponseTransformer
public class JsonResponseTransformer implements ResponseTransformer {
private static final Gson gson = new Gson();
// Gson#toJsonメソッドでオブジェクトをJSONに変換する。
@Override
public String render(Object model) throws Exception {
return gson.toJson(model);
}
}
ResponseTransformer
も@FunctionalInterface
なので、ラムダ式で実装することも可能です。
ResponseTransformer
はモデルからレスポンスの変換を抽象化する仕組みですが、同じようにリクエストからモデルへの変換を抽象化する仕組みがSparkにはなく、ちょっと残念です。次回以降、リクエストを抽象化する仕組みについても触れていこうと思っています。
これで必要なピースは揃ったので、あとはリクエストマッピングの設定を行うだけです。
- Application.java
public class Application {
private static final ResponseTransformer transformer = new JsonResponseTransformer();
public static void main(String[] args) {
// configure application.
enableDebugScreen();
// configure routes.
path("/todos", () -> { // (1)
// (2)
get("", TodoController.findAll, transformer);
// (3)
get("/:id", TodoController.findOne, transformer);
post("", TodoController.create, transformer);
put("/:id", TodoController.finish, transformer);
delete("/:id", TodoController.remove, transformer);
});
// configure after filters.
after("/*", (req, res) -> res.type("application/json")); // (4)
}
}
no. | description |
---|---|
(1) |
path メソッドは、パスの階層化・グルーピングに使用します。例えば、2番目のget メソッドにはパス/:id を指定しているので、/todos/:id というパスへのリクエストがマッピングされることになります。 |
(2) | 各メソッドとパスに対するリクエストを処理するハンドラと、ハンドラの戻り値を変換するResponseTransformer に、それぞれ先ほど作成したものを指定しています。 |
(3) | パスに:id と記述していますが、これがパス変数を意味します。パスのこの部分に入力された値をRequest#params メソッドで取り出すことが可能です。 |
(4) | 最後にafter メソッドで文字通りハンドラの後に処理するフィルタを定義しています。Sparkでは、レスポンスのステータスコードやコンテントタイプなどを自動で調整してくれないので、自分でapplication/json を付与する必要があります。 |
実行
アプリを起動して、ブラウザのRESTクライアントプラグインなどでリクエストを送ってみると、レスポンスがJSONとして返却され、RESTアプリとして機能していることが分かると思います。
まとめ
REST APIとして必要なマッピングが揃っており、シンプルに実装できることが分かりました。
ただ、やはりJSONへの変換などは自動でやってくれるわけではないので、何を受け取って何を返却するかなど理解の上で作る必要がありそうですね。
次回は、今回作ったTODOリストアプリを画面アプリとして作り変えてみます。