システム開発で思うところ

Javaで主にシステム開発をしながら思うところをツラツラを綴る。主に自分向けのメモ。EE関連の情報が少なく自分自身がそういう情報があったら良いなぁということで他の人の参考になれば幸い

CargoでPayara Serverを動かす

はじめに

こちらで使用したCargoというEEサーバーを動かすための薄いラッパーライブラリについて、自分が使ってみて良かったことなどをまとめたものです。 ブログなどでコードを公開する際、特にJakarta EEのようなEEサーバーを使う前提のものは、Spring Boot や QuarkusのようなUberJarを用いるものと比べると 公開されているコードを実際に動かすまでの手間がかかって避けがちになるように思います。
Cargoを使えば、対象となるEEサーバーのダウンロードも含めて実行してくれるので プロジェクトを git clone した後、ほとんど手間なくEEサーバーで動きを含めて確認ができます。
今回は それに加えて Mavenも同時にダウンロードする Maven Wrapper(mvnw) や、H2DBも 準備しています。

EEサーバーがあって、H2DBという標準SQLが書ける*1、基本環境が さっと手元で動かせる雛形プロジェクトの事例として、以下の記事で実際にやったことを踏まえつつ説明をしていきたいと思います。

vermeer.hatenablog.jp

  • 注意点

本プロジェクトでは Payara Serverでしか動作は確認できていません*2
その点は ご了承ください。
参考に挙げている、@backpaper0さんのリポジトリでは、コードを一切書き換えることなく(!) WildFlyやTomEEを Cargoによって起動することが出来ますので、是非 そちらも参考にされると良いと思います。

リポジトリ

https://github.com/vermeer-1977-blog/jakarta-ee9-sample

の中のサブプロジェクト
https://github.com/vermeer-1977-blog/jakarta-ee9-sample/tree/main/jpa-part-0

を使って説明をしたいと思います。

実行環境

Maven Wrapper(mvnw)

なにが嬉しいの?

Cargoの前に、mvnwについて。
mvnwを使うとローカルにMavenがインストールされていなくても、コマンドを実行するだけで 必要なMavenライブラリをダウンロードをしてくれてプロジェクト管理が可能な状態にしてくれます。

Mavenのバージョンが違うという事で弊害にであったことは、私自身はありませんが ビルド環境のベースとなるライブラリが異なるという理由で ビルドが失敗するという経験は出来れば避けたいものです。 mvnwはダウンロードする対象バージョンもプロジェクト毎に固定化されるので、そういった心配もありません。

実際に動かしてみる

jpa-part-0 フォルダへ移動して

./mvnw package

をすると、Mavenダウンロードと加えて、コードをビルドして warファイルの作成までしてくれます。 作成された warファイルは targetフォルダ配下に格納されます。

自分で作りたい

利用側はMavenのインストールが不要ですが、プロジェクトを作る側はMavenのインストールが必要です。 Mavenをインストールした後で

mvn -N io.takari:maven:0.7.7:wrapper

を実行すると

.mvnというフォルダが作成されます。これが mvnwの本体です。
また、カレントフォルダには mvnwmvnw.cmd というファイルが作られます。こちらは 実行するコマンドです。

これで mvnwを使えるようになりました。

詳細は 公式を確認ください。
https://github.com/takari/maven-wrapper

注意点

mvnwを使うだけであれば、上記の通りで良いのですが、このままだと Gitの管理対象外のままなので.gitignoreへ以下を追記します *3

#exclude
!maven-wrapper.jar

を追記して、mvnwの本体をリポジトリの管理対象に入れてください。

Cargo

ようやく本題のCargoです。
コンテナのデータ取得から設定まで pom.xmlで完結します。 pom.xmlの どこで 何をしているのか Cargo周りを中心に説明します。 DB周りや個々の設定意図については、後述します。

なにが嬉しいの?

DockerのようにOSイメージまでダウンロードをせず、「JVMが動くこと」を前提とした それ以降のレイヤーの資産をダウンロードおよび実行までしてくれるコンテナが準備可能という事です(つまり軽い)。

たとえば ちょっとしたデモ環境を作りたいときに 実行環境となるPCの環境設定を一切変えることなく EEアプリを動かせます*4

pom.xml(抜粋)

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.cargo</groupId>
            <artifactId>cargo-maven3-plugin</artifactId>
            <version>1.9.4</version>
            <configuration>
                <configuration>
                    <datasources>
                        <datasource>
                            <jndiName>jdbc/DemoDS</jndiName>
                            <driverClass>org.h2.Driver</driverClass>
                            <url>jdbc:h2:mem:demo;DB_CLOSE_DELAY=-1</url>
                            <username>demouser</username>
                            <password>demosecret</password>
                        </datasource>
                    </datasources>
                </configuration>
                <container>
                    <dependencies>
                        <dependency>
                            <groupId>com.h2database</groupId>
                            <artifactId>h2</artifactId>
                        </dependency>
                    </dependencies>
                    <systemProperties>
                        <javax.persistence.schema-generation.database.action>create</javax.persistence.schema-generation.database.action>
                        <javax.persistence.schema-generation.scripts.action>create</javax.persistence.schema-generation.scripts.action>
                        <javax.persistence.schema-generation.scripts.create-target>${project.basedir}/target/create.sql</javax.persistence.schema-generation.scripts.create-target>
                        <javax.persistence.sql-load-script-source>META-INF/tmp/initializer.sql</javax.persistence.sql-load-script-source>
                        <eclipselink.logging.level>FINE</eclipselink.logging.level>
                    </systemProperties>
                </container>
            </configuration>
        </plugin>
    </plugins>
</build>

<profiles>
    <profile>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <id>payara</id>
        <properties>
            <cargo.maven.containerId>payara</cargo.maven.containerId>
            <cargo.maven.containerUrl>https://repo.maven.apache.org/maven2/fish/payara/distributions/payara/5.2021.4/payara-5.2021.4.zip</cargo.maven.containerUrl>
        </properties>
    </profile>
</profiles>

maven(build)

Cargoのプラグイン(cargo-maven3-plugin)、build定義に追記します。

datasource

利用するDBの設定をします。 この設定は「Cargoで起動したEEサーバーの設定」です。
Javaアプリ(persistence.xml)とEEサーバーとの関連付けは、 <jndiName>jdbc/DemoDS</jndiName> です。

なので、usernamepasswordを demouser や demosecret から変更しても、実際のところ 問題なくCargoで起動したWebアプリは動きます*5

じゃぁ、そのDB本体(ミドルウェア)は どこから入手しているの?というのは後述します(環境設定周り)。

systemProperties

システムプロパティも指定が可能です。
このシステムプロパティも先ほどと同様に「Cargoで起動したEEサーバーの設定」です。

javax.persistence.の記載の意図などについては後述します(環境設定周り)。

maven(profile)

利用する EEサーバーの本体となるファイルのダウンロードおよび、コマンド実行時の挙動を設定します。

id

コマンドでパラメータ指定する際に利用します。

複数のEEサーバーを定義した際には <activeByDefault>true</activeByDefault>を付与している要素がデフォルトのEEサーバーとして起動できます。

任意のサーバーをパラメータで指定する場合は

./mvnw cargo:run -Ppayara

のように idを指定すれば実行できます。

properties

Payara Serverのzipファイルの取得先を指定します。

cargo:runした時に、zipファイルがダウンロードされていなければジョブの開始時点でダウンロードをします。ダウンロードして展開したEEサーバーの実体は実行パスのカレントに作成される targetフォルダの中にあります。

ダウンロード先や設定の詳細は公式を確認してみてください。

https://codehaus-cargo.github.io/cargo/Home.html

ちなみに fileプロトコルを使えば ローカルに保存しているzipファイルを取得先として指定できます*6

<cargo.maven.containerUrl>file:/home/hoge/Downloads/payara-5.2021.4.zip</(...)>

Windowsであればfile:c:\(以下略)という感じで指定すれば出来ると思います(未検証)。

注意点

Cargoが利用するEEサーバーの実体は targetフォルダにあるため ./mvnw cleanなどでtargetフォルダを削除した場合、再ダウンロードをします。
検証中で なんども cleanを実行するけれど ダウンロードの頻度は避けたい時は、上記の fileプロトコルを使った取得に書き換えると作業が捗ります。

環境設定周り

コンテナに関する環境設定の詳細と意図について説明したいと思います。

なにが嬉しいの?

  • コンテナで利用するDB(H2)を立ち上げる(個別にインストールしなくて良い)
  • @Entityクラスと同期のとれたテーブルが生成される
  • データの初期登録もできる
  • 上記の設定に関する記述をコードに記述しなくて良い
  • warファイルに設定が含まれないので、独立して準備したEEサーバーや開発環境のために書き直しなどをしなくて良い

H2を利用する

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.200</version>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>     

実行時依存として追加しておくことで、コンテナ起動時にH2をDBとして使えるようになります。 接続に関する説明は既出のため割愛します。

@EntityクラスからDBを生成

<javax.persistence.schema-generation.database.action>create</(...)> で@Entityクラスの実装から、コンテナ起動時にH2へテーブルを生成します。

もしDDLがある場合は、そちらを使ってテーブル生成しても良いと思います(その際のプロパティについては割愛します)。

DBからDDLを生成

<javax.persistence.schema-generation.scripts.action>create</(...)> でDBからテーブル生成DDLを作成します。

<javax.persistence.schema-generation.scripts.create-target>${project.basedir}/target/create.sql</(...)> は生成したDDLの出力先です。

このDDLを使えば、コンテナ起動時に自動生成されるDBを手元のDB環境で作ることも出来ます。(この辺りの意図は後述(開発IDEとの連携)で)

DBへ初期データを登録

<javax.persistence.sql-load-script-source>META-INF/tmp/initializer.sql</(...)> でコンテナ起動時にデータを初期登録できます。
指定パスは クラスパス配下です*7

SQLの実行ログ

<eclipselink.logging.level>FINE</(...)>

この辺りは実装依存です。
Payara Serverは JPAの実装が EclipseLinkなので このように指定します。 Hibernate が実装となっているEEサーバーの場合は指定方法が違います。

開発IDEとの連携

Cargoとは直接関係ないのですが、これまでの設定は この「開発IDEとの連携」を見越して行ったものもあるので説明をしたいと思います。

EEサーバー

Cargoは実行環境ではありますが、開発時に接続するサーバーと比べて あれこれとしようと思うと少し手間がかかります。
たとえばIDEによるデバッグモード起動などはローカルにインストールしたEEサーバーを使用する方が、既知の開発方法を流用できるので便利です。

Cargoの設定は Cargoに閉じています*8
IDEにてローカルにインストールしたEEサーバーを選択して これまで通り開発ができます。

テーブル生成

CargoではH2を使いましたが、本番環境では異なるDBかもしれません。
またCargoでは テストのために作り込んだDBの状態が起動の都度クリアされてしまいます。

Jakarta Persistence(旧JPA)のJPQLや 標準SQLの範囲で、適切に抽象化されていれば DB製品が異なっても利用可能です*9
ということで、Cargo管理下とは別にDBをつくって開発を進めましょうということになるわけですが、空のDBは各DBの手順に従うとして、その先のテーブル生成はどうしましょう?

<javax.persistence.schema-generation.database.action>create</(...)>を使って、@Entityから生成をするという方法もあるでしょう。
では DDLを使ってテーブルを生成したい場合は、どうしましょうか?

ということで<javax.persistence.schema-generation.scripts.(...)で作成したDDLを使いましょう。

卵とヒヨコみたいな感じですが、とにかく入手したDDLを元に開発環境や本番環境のテーブル生成ができます。

なお、Cargoで設定した時には、その値に特に重きを置いていなかった、usernamepasswordですが、 EEサーバーで JDBCのコネクションプールの設定や、直接DBへの接続をするためには必要な情報となります。

DBの接続設定

DBの接続情報を、利用するEEサーバーにも設定をしましょう。
JNDIは Cargoと同じ jdbc/DemoDS としてください。こうすることでDBの実体は違ってもプロジェクトの定義を変えることなく起動できます。
IDとパスワードは前項の「テーブル生成」で設定した値を使用してください。

なお、IDおよびパスワードですが、Cargoで設定したものとは必ず別の値にしましょう。
リポジトリ管理される資産に有効なIDとパスワードは記録すべきではありません。

参考

github.com

GitHub - takari/maven-wrapper: The easiest way to integrate Maven into your project!

Codehaus Cargo - Home

事のキッカケの自tweet

さいごに

Jakarta PersistenceのNativeQueryを使う記事で、初めてCargoを使いました。 他にも色々と便利な機能などありそうな気がしますが、DBを使うアプリの実行および開発環境準備まで含めた やっておきたいことは試せたように思います。
可能な限り、今後の Jakarta EEに関するブログを書くときには、Cargoを使って動くサンプル実装が作られたらと思います。

*1:PosgreSQL互換モードもある

*2:というか TomEEなどでは動かないという確認もしています

*3:Java向けの設定では一般的に jarファイルを対象外としています

*4:JDKのインストールは必要だけど

*5:実際に変えてみて動くことまでは確認しました

*6:当然ですが、一度は自分でローカルにzipファイルを保存しておく必要はあります

*7:Javaコードから読み込める場所にないといけないですからね

*8:portの衝突があるので、コンテナは終了させておきましょう

*9:厳密に言えば「EEの仕様としては そのように推定される」という言い方になるかもしれませんが