r-kaga's log

TopBlogBookmarks

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

© 2021, Built with Gatsby