【2019年11月版】Quarkus + Panache で CosmosDB as MongoDB に入門


Quarkus の Panache-Mongo で簡単 CosmosDB 入門

今回は Azure の分散マルチモデルDBの CosmosDB をDBとして、いつもの Quarkus + Panache で簡単 REST API を構築してみたいとおもいます。
CosmosDB は"マルチモデル"なので様々なインタフェースを経由してアクセスが可能です。(インスタンス作成時にどのモデルを使用するか選択があり、作成後はそのモデルでのアクセスとなるので、一つのインスタンスに対して同時に複数のモデルのAPIからアクセスできるわけではないです)。
これまでやってこなかったPanacheのMongoDB対応の機能を使ってみましょう。

1. CosmosDB のインスタンス作成

まずは Azure のポータルからCosmosDBのリソースを作成しましょう。
リストの中から "CosmosDB" を選択します。なければ cosmos などで検索です。

見つからなければ"検索" というのがどうも、ねぇ。。。

続いて CosmosDB のリソース作成時にAPIを"MongoDB"にするのを忘れないでください。

インスタンス作成後、接続文字列をコピーして控えておきます。

2. Quarkus プロジェクトの作成

まずは以下の公式ガイドを参考に、MongoDB+Panache のQuarkusプロジェクトを作成いたします。

以下の maven コマンドを叩きます。

$ mvn io.quarkus:quarkus-maven-plugin:1.0.0.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=mongodb-panache-quickstart \
    -DclassName="org.acme.mongodb.panache.FruitResource" \
    -Dpath="/fruits" \
    -Dextensions="resteasy-jsonb,mongodb-panache"
...
$ cd mongodb-panache-quickstart

いつもの通り・・・って!!! quarkus-maven-plugin:1.0.0.Final ですよ!ついに正式版きましたー!!!(今、githubのリポジトリ確認したら 1.0.1.Final出てましたが・・・)

続いて MongoDB の接続先として先ほど控えておいた CosmosDB の接続文字列を application.properties に追記します。

mongodb-panache-quickstart/src/main/resources/application.properties
# configure the MongoDB client for a replica set of two nodes
quarkus.mongodb.connection-string = mongodb://.............
# mandatory if you don't specify the name of the database using @MongoEntity
quarkus.mongodb.database = person

3. Entityクラスの作成

いつものように Person エンティティを作成します。今回は PanacheMongoEntity をベースクラスにします。
またクラスのアノテーションで保存先のコレクションを"ThePerson"としています。

src/main/java/org/acme/mongodb/panache/model/Person.java
package org.acme.mongodb.panache.model;

import java.time.LocalDate;

import org.bson.codecs.pojo.annotations.BsonProperty;

import io.quarkus.mongodb.panache.MongoEntity;
import io.quarkus.mongodb.panache.PanacheMongoEntity;

@MongoEntity(collection="ThePerson")
public class Person extends PanacheMongoEntity {
    public enum Status {
        Alive, DECEASED
    }
    public String name;

    // will be persisted as a 'birth' field in MongoDB
    @BsonProperty("birth")
    public LocalDate birthDate;

    public Status status;
}

@BsonPropertyアノテーションによりbirthDateフィールドのBSONでのキーをbirthとしています。
続いて REST API の実装に参りましょう。

4. REST API の実装

以下のようにシンプルに POSTGETだけのAPIを作成いたします。

src/main/java/org/acme/mongodb/panache/PersonResource.java
package org.acme.mongodb.panache;

import javax.transaction.Transactional;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.acme.mongodb.panache.model.Person;
import org.bson.types.ObjectId;

@Path("/person")
@Produces(MediaType.APPLICATION_JSON)
public class PersonResource {

    @POST
    public Person create(Person person) {
        person.persist();
        return person;
    }

    @GET
    @Path("/{id}")
    public Person get(@PathParam("id") ObjectId id) {
        return Person.findById(id);
    }
}

ほとんどHibernateの時と同じですが、一点だけ注意があります。PanacheMongoEntityはIDをorg.bson.types.ObjectId 型としています。
よって findByIdに渡すObjectもObjectIdとしてください。
あとはRest EasyとPanacheが宜しくやってくれます。
ちなみに、現時点での mongodb-panache プラグインはトランザクションは未サポートの模様です。

5. 開発サーバーでの確認

実装が完了したところで一旦、開発サーバーで動作確認をしてみましょう。
以下のコマンドで開発サーバーを起動します。

$ mvn quarkus:dev

起動が完了したところで、まずはターミナルからcurlコマンドにてJSONデータをPOSTしてみます。

$ curl -H 'Content-Type:application/json' -d '{"name":"Alice","birth":"2010-10-11","status":"Alive"}' http://localhost:8080/person
{"id":"5ddfb90c682fc42ef21e666f","name":"Alice","status":"Alive"}

はい、idにランダム?な文字列が入ったレコードが返ってきました。これはちゃんと登録されていそうですね。
Azureポータルの管理画面、データエクスプローラーを開いてみましょう。

無事に5ddfb90c682fc42ef21e666fObjectId のデータが確認できました!ちゃんと入っているようです!

ブラウザからも http://localhost:8080/person/5ddfb90c682fc42ef21e666fを叩いてみましょう!

はい、JSONなので飾り気なしですが、5ddfb90c682fc42ef21e666f、取れましたね!

6. ネイティブ化

ネイティブ化にも挑戦してみます!

CosmosDB への接続には SSL が必須ですので以下のページを参考に、SSL対応版ネイティブビルドを行います。

と言っても Dockerfile を以下のようにするだけです。

Dockerfile
## Stage 1 : build with maven builder image with native capabilities
FROM quay.io/quarkus/centos-quarkus-maven:19.2.1 AS build
RUN mkdir -p /tmp/ssl-libs/lib \
  && cp /opt/graalvm/jre/lib/security/cacerts /tmp/ssl-libs \
  && cp /opt/graalvm/jre/lib/amd64/libsunec.so /tmp/ssl-libs/lib/
COPY mongodb-panache-quickstart/src /usr/src/app/src
COPY mongodb-panache-quickstart/pom.xml /usr/src/app
USER root
RUN chown -R quarkus /usr/src/app
USER quarkus
RUN mvn -f /usr/src/app/pom.xml -Pnative -Dmaven.test.skip=true clean package

## Stage 2 : create the docker final image
FROM registry.access.redhat.com/ubi8/ubi-minimal
WORKDIR /work/
COPY --from=build /usr/src/app/target/*-runner /work/application
COPY --from=build /tmp/ssl-libs/ /work/
RUN chmod 775 /work
EXPOSE 8080
CMD ["./application", "-Dquarkus.http.host=0.0.0.0", "-Djava.library.path=/work/lib", "-Djavax.net.ssl.trustStore=/work/cacerts"]

ページのサンプルそのままですが /tmp/ssl-libsgraalvm から cacertssunecのスタティックライブラリをコピーして、実行用イメージにてそれらを参照するように追加を行いました。

続いて、これを参照する docker-compose.ymlを作成します。

docker-compose.yml
version : "3"
services:
  quarkus:
    build:
      context: .
    ports:
      - 8080:8080

で、ビルドを行います。

$ docker-compose build

7. ネイティブの動作確認

早速、起動してみましょう。

$ docker-compose up -d

で、いつもの通りcurl でPOST します。

$ $ curl -H 'Content-Type:application/json' -d '{"name":"Alice","birth":"2010-10-11","status":"Alive"}' http://localhost:8080/person
{"id":"5ddfc8c96b679d01d9bc40c0","name":"Alice","status":"Alive"}

先ほどと違うIDでちゃんと返ってきました!

ブラウザで http://localhost:8080/person/5ddfc8c96b679d01d9bc40c0 を叩いてみます。


はい、ちゃんと動いてますね。

CosmosDBのデータエクスプローラーからも確認してみます。

2つ目のドキュメントが生成されていることが確認できましたね!
お疲れ様でした!

まとめ

駆け足でしたが Panache から MongoDB Clientを経由して CosmosDB にデータの読み書きができることが確認できました。
また、ネイティブビルドしたバイナリからでもちゃんと CosmosDB にSSL接続できることが確認できました!
早々にAzureContainerでもデプロイしてみたいですが本日はここまでといたします。

今回の成果物は以下のリポジトリにあげました。ご参考にどうぞ。