(もともとあった)マルチモジュール構成を更にリファクタしたときにドハマリしたので備忘がてらメモ。
環境
- JVM 16.0.2
- Kotlin 1.4.31
- Gradle 7.0.2
完成形
これを
├── appA │ ├── presentation │ ├── application │ ├── domain │ ├── infrastructure │ └── src └── appB ├── presentation ├── application ├── domain ├── infrastructure └── src
こうした
├── appA │ ├── presentation │ └── src ├── appB │ ├── presentation │ └── src └── modules ├── application ├── domain └── infrastructure
パッケージの最適な配置がどうなるか手探りな状態でappAの開発を始めたけど、appBも順調に伸びてきて、そろそろ共通で使えるようにしようかな、みたいなところが動機。
CQRSの考え方を取り入れていて、module/domain, module/infrastructureの下は更にcommandまたはqueryでサブモジュールになっている。(フラグ)
tips: モジュールのおまとめにディレクトリを使う
親のディレクトリはモジュールでなくていいとき
※ マルチモジュールのすゝめのkts版
// settings.gradle.kts val modulesDir = File("modules") project(":application").projectDir = File(modulesDir, "application")
参照時は :modules:application
ではなく :application
となる点に注意
tips: モジュールお引越しは先にpackageをrenameする
新しくディレクトリを切って、そこにsrc以下をmvすると依存する側が自動で追ってくれないので、IntelliJのProjectペインから該当packageを右クリック→refactor→renameでパッケージを変更してからmvすると多少楽だった。
importが追随しきれていないこともあるので、その場合は一括置換をかけてrebuildした。
ハマった: ネストしたモジュールがimplementationできない
A: 事前に親モジュールを評価しておく
// modules/application/build.gradle.kts evaluationDependsOn(":domain:foo") dependencies { implementation(project(":domain:foo:query")) }
ハマった: jar filenameのデフォルトが最下位のサブモジュール名
:domain:foo:query
:infrastructure:foo:query
と最下位モジュール名が同じ場合に、 docker compose build
が以下メッセージで失敗した。
Entry BOOT-INF/lib/query-plain.jar is a duplicate but no duplicate handling strategy has been set.
IntelliJでのビルド、実行はできる状態。はて…?
ググるとGradle7へのお気持ちとtasks.Copy:duplicatesStrategy をEXCLUDEなりに設定しろというのが出てくるけど、EXCLUDE(重複した場合に取り除く)でもINCLUDE(重複した場合に上書きする)でもWARNでも、設定すると依存先のクラスがないと言われてエラーになる。
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'XXX' defined in URL [jar:file:/app.jar!/BOOT-INF/lib/presentation-plain.jar!/path/to/Controller.class]
query-plain.jar
を見てみよう
$ docker run --rm -it container-name:latest sh / cp app.jar tmp/ / cd tmp/ / jar xvf app.jar / cd BOOT-INF/lib/ / jar tf query-plain.jar # domain(or infrastructure)しかない!
諦めてモジュール名を変えてもいいけど、絶対それっぽい設定値あるでしょ…とドキュメントやbuild.gradle.ktsをコネコネしてそれっぽい着地を見る
// modules/infrastructure/foo/query/build.gradle.kts tasks.jar { enabled = true // 生成されるjar fileの名前が一意になるようにする archiveBaseName.set("infrastructure-foo-query") }
※ 直接文字列で指定しなくてもなんらかいい方法ありそう
※ Groovy式だと直接代入しているが、Kotlinで記法が変わった様子
参考
Spring Boot 2 (Kotlin) + Gradle Kotlin DSL でマルチモジュールを実現してみた - Qiita ServerSide Kotlin Appをマルチモジュール化する - ロコガイド テックブログ