QuarkusでJSONでレスポンスを返すAPIを作成してみる
05 September, 2020
Quarkusを動かしてみました。
目次
What is Quarkus
QuarkusはKubernetesネイティブなJavaフレームワークで、公式サイトにもある通り、Container Firstな設計となっています。
以下は公式からの抜粋です。
- First Class Support for Graal/SubstrateVM
- Build Time Metadata Processing
- Reduction in Reflection Usage
- Native Image Pre Boot
Dockerやk8sなどコンテナ環境の利用が常識となってきた事で生まれてきたフレームワークかと思います。
最も利用されているJavaフレームワークとしてSpringがあり、私もSpring Bootでk8sにデプロイするようなAPIを開発する事が日常になりましたが、メモリ使用量等の観点から、コンテナ環境での利用においてはGo言語の方が採用例が多い認識です。
個人的にはKotlinも好きなので、Quarkusでこの辺りが改善されるならコンテナ環境でのマイクロサービス構築時の言語選択肢としてJavaやKotlinがより魅力的になるので、割と嬉しそうです。
Spring互換が割とありそうな感じなのも嬉しいですね。
Quarkus for Spring Developers
どこまでSpringからシェアを奪えるかは分かりませんが、Quarkusに限らない話ですが、個人的にはコンテナの利用が今後も続く限りは、コンテナ環境に適したフレームワークの受け入れは当然進むだろうと捉えており、ある程度キャッチアップしておきたく色々と試してみようと思います。
サンプルコード
とりあえずシンプルなJSONレスポンスを返すAPIを作成します。
まずはプロジェクトの作成からです。
$ mvn io.quarkus:quarkus-maven-plugin:1.6.1.Final:create \
-DprojectGroupId=com.rkaga \
-DprojectArtifactId=rest-json-api-sample-implementation \
-DclassName="com.rkagaya.rest.json.BookResource" \
-Dpath="/books" \
-Dextensions="resteasy-jsonb"
extensionsでjacksonを使う事もできます
mvn io.quarkus:quarkus-maven-plugin:1.6.1.Final:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=rest-json-quickstart \
-DclassName="org.acme.rest.json.FruitResource" \
-Dpath="/fruits" \
-Dextensions="resteasy-jackson"
上記コマンドで作成したアプリケーションに自動的に以下のクラスが生成されます。
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/books")
public class BookResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}
}
まずはこの状態でアプリケーションを立ち上げてみます。
$ mvn quarkus:dev
レスポンスが返ってくる事を確認できました
➜ curl http://localhost:8080/books
hello%
次にJSONでレスポンスを返すようにします。
package com.rkagaya.rest.json;
import org.jboss.logging.annotations.Param;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.*;
import java.util.stream.Collectors;
@Path("/books")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class BookResource {
private Set<Book> list =
Collections.newSetFromMap(Collections.synchronizedMap(new LinkedHashMap<>()));
public BookResource() {
list.add(new Book(1, "name1", "description1"));
list.add(new Book(2, "name2", "description2"));
}
@GET
public Response getList() {
return Response.ok(list).build();
}
@GET
@Path(value = "/{bookId}")
public Response get(
@PathParam(value = "bookId") int booKId
) {
System.out.println(booKId);
List<Book> result = list.stream()
.filter(book -> book.getId() == booKId)
.collect(Collectors.toList());
return Response.ok(result).build();
}
@POST
public Response create(
Book book
) {
list.add(book);
return Response.status(201).build();
}
@DELETE
@Path(value = "/{bookId}")
public Response delete(
@PathParam(value = "bookId") int booKId
) {
list.removeIf(existingBook -> existingBook.getId() == booKId);
return Response.noContent().build();
}
}
Bookオブジェクトも作ります。
package com.rkagaya.rest.json
data class Book(
val id: Int,
val name: String,
val description: String
)
➜ ~ curl -v http://localhost:8080/books | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /books HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Length: 107
< Content-Type: application/json
<
{ [107 bytes data]
100 107 100 107 0 0 7642 0 --:--:-- --:--:-- --:--:-- 7642
* Connection #0 to host localhost left intact
* Closing connection 0
[
{
"description": "description1",
"id": 1,
"name": "name1"
},
{
"description": "description2",
"id": 2,
"name": "name2"
}
]
➜ ~ curl -v http://localhost:8080/books/1 | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /books/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Length: 54
< Content-Type: application/json
<
{ [54 bytes data]
100 54 100 54 0 0 1928 0 --:--:-- --:--:-- --:--:-- 2000
* Connection #0 to host localhost left intact
* Closing connection 0
[
{
"description": "description1",
"id": 1,
"name": "name1"
}
]
➜ ~ curl -X POST -v -H "Content-Type: application/json" -d '{"name":"name3", "id": 4, "description":"description3"}' localhost:8080/books
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /books HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 55
>
* upload completely sent off: 55 out of 55 bytes
< HTTP/1.1 201 Created
< Content-Length: 0
<
* Connection #0 to host localhost left intact
* Closing connection 0
➜ ~ curl -v -X DELETE http://localhost:8080/books/1 | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> DELETE /books/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 204 No Content
<
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
* Connection #0 to host localhost left intact
* Closing connection 0
終わりに
雑ですが、ひとまずJSONでレスポンスをかえす所まで試しました。 次はDI周りかなと思います。
参考
https://github.com/quarkusio/quarkus-quickstarts https://github.com/r-kgy/quarkus-rest-api-sample-implementation.git