はじめに
Domaの一番の特徴であり、鬼門でもあるのが注釈処理(アノテーションプロセッシング)です。
最新のDoma 2.44.1を前提に記述しますが、基本的にはどのバージョンにも当てはまる内容となっています。
Domaの他の機能紹介についてはDoma入門もお読みください。
注釈処理とは
注釈処理を使うと、コンパイル時にソースコードに付与されたアノテーションを読み取って、別のソースコードやバイトコードを生成できます。
Domaはソースコードを生成するタイプのフレームワークですが、例えばMicronautはバイトコードを生成しています。
利用者視点で見ると、ソースコードを生成するタイプはコードを読んだりブレイクポイントを置いたりできるのでデバッグしやすく、バイトコードを生成するタイプはコンパイル時間をスキップできるという特長があります。
注釈処理の利点
利点を大別すると3つあるのではと思います。
- ボイラープレートコードを削減できる
- コンパイル時にエラーを検出できる
- 実行時のブートストラップ(初期化)を高速化できる
ボイラープレートコードを削減できる
ボイラープレートコード削減は注釈処理に限った利点ではなく、例えばリフレクションなどでも実現できます。この点に関しては、AutoValueというプロダクトが、注釈処理と他の手法を比較していて参考になります。特にこのスライドが面白いです。
AutoValueのスライドでは触れられていませんが、リフレクションに限って言えば、ネイティブイメージ化を考慮する場合においては注釈処理の利点は大きいと言えます。ネイティブイメージを作るにあたってのリフレクションの制約を回避できるからです。
コンパイル時にエラーを検出できる
この点については「Domaの開発で大切にしている10のこと」という記事の動かさないとわからないを減らすで紹介しました。
実行時のブートストラップの高速化
なぜ、実行時のブートストラップの高速化が可能かというと、注釈処理を使わなければ実行時に生成してメモリに保持するであろうメタデータをコンパイル時にクラスとして生成できるからです。
ブートストラップに比較的時間がかかるDBアクセスフレームワークの代表格はHibernateでしょう。Hibernateは、SessionFactory(JPAとして使う場合はEntityManagerFactory)を作るのにエンティティクラスの情報をリフレクションで読み取り様々な処理をします。エンティティクラスの数が少ない場合は全く気にならないと思いますが、数が多くなってくると顕著に初期化に時間がかかることに気づくでしょう。ログレベルをtraceなどにするとどんなことをしているのがよくわかると思います。
QuarkusのHibernate Extensionではその辺りの対策されているかもと思ってエンティティ100個くらい作ってサンプル動かしましたが、起動までに数十秒かかったので特に対策は入っていなさそうでした(JVM modeで試したのですが、Native modeならまた違ってくる可能性があります)。
一方、Domaの場合、実行時にはすでに存在するクラスをロードするだけで必要なメタデータを入手できます。しかも、ブートストラップ時に一度にロードするのではなく、必要に応じてクラスをロードするようになっているためエンティティクラスの数が多くなってもブートストラップ時間が伸びるということはありません。
注釈処理で気をつけたいこと
最も気をつけたいのは、注釈処理を有効にするためにIDEが特殊な設定を要求することです。また、ビルドツールにMavenを使うかGradleを使うかによっても気をつけるところが変わってきます。
冒頭で鬼門と書きましたが、Domaが動かないという現象のほとんどがこの設定に関することのように見受けられます。
おすすめのIDEとビルドツール
IDEでは、EclipseとIntelliJ IEDAのどちらでも動作確認しています。どちらが良いかは完全に好みですが、注釈処理を動かすにはEclipseの方が気を付けないといけないことが多いです。ただ、コツをつかめば大したことはないですし、Eclipseのインクリメンタルコンパイルによる注釈処理の実行はIDEAを使った場合に比べて迅速なフィードバックなので、気をつけるポイントが多いからという理由だけでEclipseを切り捨てるのは惜しいと思います(IDEAの場合は明示的にビルドしないと注釈処理が動かないが、Eclipseの場合はファイルをセーブするたびに動き、注釈処理によるエラーが迅速に開発者にフィードバックされます)。
ビルドツールは、GradleとMavenで動作確認しています。これもどちらでも好きな方を使っていただければいいのですが、EclipseとMavenの組み合わせは私個人でほとんどノウハウを持っていません。どうもM2Eclipseの挙動でハマることが多いようです。ただ、原因はSQLファイル周りだということがわかっているので、Criteira APIだけしか使わないのであればそんなに困らないかもしれません。
なお、GradleやMavenを使わずにEclipseやIEDAで動作させることも可能ですが全くおすすめしません。依存ライブラリをはじめ様々な設定で絶対にハマるのでGradleかMavenのどちらかは必ず使ってほしいところです。
まとめると、こんな感じになります。
IDE | ビルドツール | 説明 |
---|---|---|
Eclipse | Gradle | 後述します。 |
Eclipse | Maven | ノウハウ不足。本記事では言及しない。情報募集中。 |
IDEA | Gradle | GradleプロジェクトとしてインポートすればOK。 |
IDEA | Maven | MavenプロジェクトとしてインポートすればOK。 |
Eclipse と Gradle を組み合わせる場合のポイント
まず、Gradleでプロジェクトを作ります。EclipseではなくGradleのプロジェクトとしてまず動作させることが重要です。雛形としては下記のプロジェクトを使うことをオススメします。
このプロジェクトはGradleのマルチプロジェクト構成になっています。もし作成したいものがマルチプロジェクト構成を必要としていなくても、このマルチプロジェクト構成は維持しておいて良いと思います。また、このプロジェクトはKotlin DSLで作られていますが、これも同様にリファクタリングのしやすさなどを考えてこのままKotlin DSLを使い続ける(Groovy DSLで書き換えない)で良いと考えます。さらに、このプロジェクトはGradle Wrapperを使っていますが、これも使わない理由はないのではと思います。
Gradleプロジェクトで最も重要なことは、com.diffplug.eclipse.aptというGradleプラグインを使うことです。このプラグインはEclipseで注釈処理を動かすための設定を出力してくれます。このプラグインはGradleのEclipseプロジェクトをフックするので、必要な設定ファイルを出力するには、./gradlew eclipse
を実行すればOKです。念のため、常にcleanEclipseタスクとセットで
./gradlew cleanEclipse eclipse
とするのがオススメです。
上述の設定ファイルを出力して初めてEclipseにプロジェクトをインポートします。EclipseはGradleのマルチプロジェクトを認識しないので、上述の雛形を使ったケースでは子プロジェクトのみEclipseにインポートされますが、特に問題にはならないと認識しています。
開発を進めていく過程で、依存ライブラリのバージョンが上がったり、注釈処理の設定を変更する必要が出てくるかもしれません。その場合、Eclipseから修正するのではなく、まずGradleのビルドスクリプトを修正します。その後に./gradlew cleanEclipse eclipse
を実施してEclipseの設定ファイルを再出力し、Eclipseプロジェクトをリフレッシュ(設定ファイルのリロード)してください。常にGradleのビルドスクリプトを正とするのが非常に重要です。
おわりに
Domaの注釈処理について説明しました。
最近ZulipというチャットサービスにDoma専用のルームを開設しました。もしDomaに関して質問などあればこのチャットルームでお気軽にどうぞ。
例えば、注釈処理の設定をGradleのビルドスクリプトに記述する方法などは世の中に情報少なめなので困ることあるかもしれませんが、質問もらえればアドバイスできそうです。
もちろん、DomaやDomaの関連プロダクトの開発に協力いただける方や情報を共有いただける方の参加もお待ちしています。