QuarkusのDevServicesを試す

はじめに

先日、Quarkusの1.13がリリースされので、リリースブログを眺めていたのですがDevServicesという便利そうな機能が追加されていたので試してみようかと思います。

DevServicesとは

DevモードでQuarkusを起動した場合、追加の設定無しでDBを起動してくれてDevモードのコンフィグとバインドしてくれるようです。
例えば、PostgresSQL JDBCの拡張がPomの依存に追加されている場合Testcontainersを使って(もしくはJavaのプロセス内で)自動的にDBを立ち上げてくれます。(今回は試しませんがReactiveクライアントにも対応しているようです)
現在DevSercicesは以下のDBに対応しているようです。

また、DB2MSSQLに関してはライセンスへの同意が必要です。src/main/resources/container-license-acceptance.txtを作成して以下のようなテキストを記述する必要があります。

ibmcom/db2:11.5.0.0a
mcr.microsoft.com/mssql/server:2017-CU12

使ってみる

環境

今回は以下の環境でアプリを動かします。

$ java --version
openjdk 16 2021-03-16
OpenJDK Runtime Environment (build 16+36-2231)
OpenJDK 64-Bit Server VM (build 16+36-2231, mixed mode, sharing)

$ mvn --version
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 16, vendor: Oracle Corporation, runtime: /home/username/.sdkman/candidates/java/16-open
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-70-generic", arch: "amd64", family: "unix"

$ docker version
Client: Docker Engine - Community
 Version:           20.10.5
 API version:       1.41
 Go version:        go1.13.15
 Git commit:        55c4c88
 Built:             Tue Mar  2 20:18:20 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.5
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       363e9a8
  Built:            Tue Mar  2 20:16:15 2021
  OS/Arch:          linux/amd64
  Experimental:     true
 containerd:
  Version:          1.4.4
  GitCommit:        05f951a3781f4f2c1911b05e61c160e9c30eaa8e
 runc:
  Version:          1.0.0-rc93
  GitCommit:        12644e614e25b05da6fd08a38ffa0cfe1903fdec
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

プロジェクトを作成する

今回はPosgresSQLを利用して、DBに保存されたデータを返す簡単なAPIを記述しようと思います。
以下の構成でプロジェクトを作成します。

f:id:yuya_hirooka:20210405140316p:plain

Devモードでアプリケーションを起動する

DevServcesはデフォルトでオンになっており、DB URLやパスワード、ユーザ名が設定されていなければTestcontainersを使ってDBを立ち上げてくれます。
先程、作成したプロジェクトをunzipしてなにも変更せずにDevモードで起動すると以下のようなログを出力してDBも立ち上げてくれます。

$ ./mvnw compile quarkus:dev
[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------------< dev.hirooka:devservices >-----------------------
[INFO] Building devservices 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- quarkus-maven-plugin:1.13.0.Final:generate-code (default) @ devservices ---
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ devservices ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ devservices ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- quarkus-maven-plugin:1.13.0.Final:dev (default-cli) @ devservices ---
Listening for transport dt_socket at address: 5005
2021-04-05 12:13:24,948 INFO  [org.tes.doc.DockerClientProviderStrategy] (build-28) Loaded org.testcontainers.dockerclient.EnvironmentAndSystemPropertyClientProviderStrategy from ~/.testcontainers.properties, will try it first
2021-04-05 12:13:25,299 INFO  [org.tes.doc.DockerClientProviderStrategy] (build-28) Found Docker environment with Environment variables, system properties and defaults. Resolved dockerHost=unix:///var/run/docker.sock
2021-04-05 12:13:25,300 INFO  [org.tes.DockerClientFactory] (build-28) Docker host IP address is localhost
2021-04-05 12:13:25,324 INFO  [org.tes.DockerClientFactory] (build-28) Connected to docker: 
  Server Version: 20.10.5
  API Version: 1.41
  Operating System: Ubuntu 20.04.2 LTS
  Total Memory: 31741 MB
2021-04-05 12:13:25,326 INFO  [org.tes.uti.ImageNameSubstitutor] (build-28) Image name substitution will be performed by: DefaultImageNameSubstitutor (composite of 'ConfigurationFileImageNameSubstitutor' and 'PrefixingImageNameSubstitutor')
2021-04-05 12:13:25,352 INFO  [org.tes.uti.RegistryAuthLocator] (build-28) Failure when attempting to lookup auth config. Please ignore if you don't have images in an authenticated registry. Details: (dockerImageName: testcontainers/ryuk:0.3.1, configFile: /home/yuya-hirooka/.docker/config.json. Falling back to docker-java default behaviour. Exception message: /home/yuya-hirooka/.docker/config.json (そのようなファイルやディレクトリはありません)
2021-04-05 12:13:26,102 INFO  [org.tes.DockerClientFactory] (build-28) Ryuk started - will monitor and terminate Testcontainers containers on JVM exit
2021-04-05 12:13:26,103 INFO  [org.tes.DockerClientFactory] (build-28) Checking the system...
2021-04-05 12:13:26,103 INFO  [org.tes.DockerClientFactory] (build-28) ✔︎ Docker server version should be at least 1.6.0
2021-04-05 12:13:26,185 INFO  [org.tes.DockerClientFactory] (build-28) ✔︎ Docker environment should have more than 2GB free disk space
2021-04-05 12:13:26,295 INFO  [🐳 .6.12]] (build-28) Creating container for image: postgres:9.6.12
2021-04-05 12:13:26,337 INFO  [🐳 .6.12]] (build-28) Starting container with ID: 33b42f67a20ed4d4ff1b2f0135ec06b13c3444e5782567e4d30395e0c5741fd3
2021-04-05 12:13:26,710 INFO  [🐳 .6.12]] (build-28) Container postgres:9.6.12 is starting: 33b42f67a20ed4d4ff1b2f0135ec06b13c3444e5782567e4d30395e0c5741fd3
2021-04-05 12:13:30,167 INFO  [🐳 .6.12]] (build-28) Container postgres:9.6.12 started in PT3.98152593S
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2021-04-05 12:13:30,588 INFO  [io.quarkus] (Quarkus Main Thread) devservices 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.13.0.Final) started in 6.064s. Listening on: http://localhost:8080
2021-04-05 12:13:30,589 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2021-04-05 12:13:30,589 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, jdbc-postgresql, mutiny, narayana-jta, resteasy, smallrye-context-propagation]

docker psコマンドで確認してみるとPostgresとTestcontainersのコンテナがきどうしているのが確認できます。

$ docker ps
CONTAINER ID   IMAGE                       COMMAND                  CREATED         STATUS         PORTS                     NAMES
74541a57e8cd   postgres:9.6.12             "docker-entrypoint.s…"   3 minutes ago   Up 3 minutes   0.0.0.0:49164->5432/tcp   great_blackburn
72f55524eecf   testcontainers/ryuk:0.3.1   "/app"                   3 minutes ago   Up 3 minutes   0.0.0.0:49163->8080/tcp   testcontainers-ryuk-42c2bcf5-1ddc-415d-b694-3f7f311d885e

データを取得してみる

それでは、実際にこの起動したDB利用してみようと思います。
まずは初期データを投入します。
まずはapplication.propertiesを以下のように修正します。

%dev.quarkus.hibernate-orm.log.sql=true
%dev.quarkus.hibernate-orm.database.generation=drop-and-create

quarkus.hibernate-orm.database.generation=drop-and-createを指定することで、Quarkusのアプリは起動時にsqlファイルを読み取ってテーブルを作成してくれるようになります。
また、デフォルトではresourcesimport.sqlという名前のファイルを読み取るようになっているのでそちらも用意しておきます。

drop table if exists message;

create table message(
    id      int,
    content varchar(100)
);

insert into message
values (1, 'hello, dev service1'),
       (2, 'hello, dev service2'),
       (3, 'hello, dev service3');

quarkus.hibernate-orm.log.sqlSQLの実行がわかりやすいように設定しています。また%devはDevモードで起動時に有効になる設定を示します。

この状態でアプリケーションを再起動します。
すると、ここには記述しませんが実行されたSQLのログが出力されるはずです。

ここまでで、起動したDBを利用できていることががある程度確認できたかもしれませんが。
EntityとControllerを作成してmessageを取得してみようと思います。

MessageEntity.java

@Entity
public class MessageEntity {

    @Id
    private int id;

    private String content;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

GreetingResource .java

@Path("/messages")
public class GreetingResource {
    private EntityManager em;

    public GreetingResource(EntityManager em) {
        this.em = em;
    }


    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<MessageEntity> hello() {
        return em.createNativeQuery("select id, content from message", MessageEntity.class).getResultList();
    }
}

コントローラーはデフォルトで作成されたGreetingResource.javaを少し改造して作っています。また、少し筋は悪いかもしれませんがEntityをそのままレスポンスとして返すようにしています。

アプリケーションがリロードされたらcURLを叩いてみます。

$ curl localhost:8080/messages
[{"id":1,"content":"hello, dev service1"},{"id":2,"content":"hello, dev service2"},{"id":3,"content":"hello, dev service3"}]

import.sqlでセットしていた値が帰ってきていることが確認できました。

本番用の設定を記述する

前述したとおりDevServicesはDB URLなどがセットされている場合においてDBを起動しないようになっています。
例えば、以下のように設定を記述してアプリケーションを起動すると、DBが起動されません。

quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/hibernate_orm_test
quarkus.datasource.username=username
quarkus.datasource.password=password

起動すると、import.sqlの実行に失敗したエラーログが出力されたと思います。
application.properitesに本番用の設定値を書きたいが、ローカルでの開発ではDevServicesを利用したい場合は設定のプリフィクスとして%prodを以下のように付与します。

%prod.quarkus.datasource.db-kind=postgresql
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/hibernate_orm_test
%prod.quarkus.datasource.username=username
%prod.quarkus.datasource.password=password

これで上記の設定はDevモードでは用いられることはなくDevServicesはDBを立ち上げてくれます。

その他の設定値

最後にDevServicesの設定をいくつかまとめておこうと思います。完全なリストはこちら をご覧ください。

設定値 説明 デフォルト値(型)
quarkus.datasource.devservices DevServicesを明示的に有効化するかのフラグ true(boolean)
quarkus.datasource.devservices.image-name 利用するコンテナイメージ名を指定する。もしH2などコンテナを起動しないサービスの場合は影響を及ぼさない (string)

また、ここにはまとめてませんが、名前付きデータソースごとにそれぞれの設定を行なうことも可能なようです。