【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` などで検索です。
![新規リソース - Microsoft Azure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/0a788f14-6fa4-f77c-7fa4-7eeb283c04eb.png)
~~見つからなければ"検索" というのがどうも、ねぇ。。。~~

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

![Azure Cosmos DB アカウントの作成 - Microsoft Azure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/eeeddde9-aa85-0826-4e24-d5c11850aa02.png)

インスタンス作成後、接続文字列をコピーして控えておきます。
![CosmosDB 接続文字列 - Microsoft Azure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/2410f07c-134a-e3b6-b906-152c8a648d3b.png)

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

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

- [QUARKUS - SIMPLIFIED MONGODB WITH PANACHE](https://quarkus.io/guides/mongodb-panache)

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

```shell
$ 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` に追記します。

```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"としています。

```java: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 の実装

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

```java: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. 開発サーバーでの確認

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

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

```shell
$ 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ポータルの管理画面、データエクスプローラーを開いてみましょう。
![CosmosDB - データ エクスプローラー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/aacf2eb6-5920-cca3-e9c2-5a1cb2283af7.png)
無事に`5ddfb90c682fc42ef21e666f` の`ObjectId` のデータが確認できました！ちゃんと入っているようです！

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

![localhost 8080 person 5ddfb90c682fc42ef21e666f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/317df364-b6cf-3821-da0f-fa2115221e2f.png)

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

## 6. ネイティブ化

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

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

- [QUARKUS - USING SSL WITH NATIVE EXECUTABLES # Working with containers](https://quarkus.io/guides/native-and-ssl#working-with-containers)

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

```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-libs` に `graalvm` から `cacerts` と `sunec`のスタティックライブラリをコピーして、実行用イメージにてそれらを参照するように追加を行いました。

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

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

で、ビルドを行います。

```shell
$ docker-compose build
```

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

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

```shell
$ docker-compose up -d
```

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

```shell
$ $ 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` を叩いてみます。

![localhost 8080 person 5ddfc8c96b679d01d9bc40c0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/89da7048-2161-285f-5813-cb85400d8822.png)
はい、ちゃんと動いてますね。

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

![Cosmos- データ エクスプローラー２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/a4e4d0e3-dcbf-9921-4f9b-53b5293d77a0.png)

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

## まとめ

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

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

- [quarkus-examples branch panache-mongo-cosmos](https://github.com/Yoshinori-Koide-PRO/quarkus-examples/tree/panache-mongo-cosmos)