日々常々

ふつうのプログラマがあたりまえにしたいこと。

開発時に実際にProfileをどう指定するか

導入

SpringBootでProfileを使用するようになると、何かのprofileを指定しないと起動しなくなるような構成になりがちだと思います。 たとえば application-local.ymlapplication-dev.ymlapplication-prod.yml など環境ごとのプロファイルを用意する場合です。

開発効率を考えればProfile無しでも動作するようにしておくのが良いこともあるのですが、それだと設定誤りなどで本番環境で動作してしまうリスクが残ります。 たとえば無印(not default)ではH2で動作するようにしておいて、prod で実際のデータベースに接続するように上書きしていた場合。設定漏れや誤りで上書きできなくても、一見正常に動作し、実はH2だった……などが考えられます。 この問題を検知できる別の仕組みがあれば無印を使用するのも良いかもしれませんが、必ず何かしらのProfileを使用すると言う落とし所が現実かなと思っています。

Profile設定方法

SpringBootでProfileを指定する方法はいくつかありますが、基本的にプロパティと同じ方法が取れます。つまり、次のいずれかを使用すると思います。

ほかにもありますが、よく使うのはこの辺りかなと思います。

詳しくは 公式リファレンス を参照してください。使ったことがある物もありますが、使ったことのない物もたくさんあります。

開発時のProfile指定

開発時のProfile設定方法も上記のいずれかを使用することになりますが、案外厄介です。 と言うのも、MavenやGradle自体のJavaシステムプロパティやコマンドライン引数に渡しても、ビルドツールに設定されます。

ビルドツール自体のプロセスとSpringBootのプロセスが異なるため、Javaシステムプロパティやコマンドライン引数はそのまま渡りません。 渡すためにそれぞれ固有の知識が必要になってきます。たとえば -P で受けたのをテストのプロセスに引き渡すような。 知っていれば迷わないですが、知らなければそのための調べたり試行錯誤したりすることになります。

結局私がどうしているか

環境変数一択になっています。コンテナ運用だと環境変数になりますし、本番の設定方法と同じにしたいって理由もありますが、実行方法にとわず設定方法が揺れないのが理由です。

Gradleだと。

SPRING_PROFILES_ACTIVE=hoge gradle bootRun

Mavenだと。

SPRING_PROFILES_ACTIVE=hoge mvn spring-boot:run

これらはシェルですが、WindowsPowerShellでも $env:SPRING_PROFILES_ACTIVE=hoge でできました。

IDEからの実行でも環境変数に追加でいけます。以下はIDEAの例。Profile複数使い分ける場合とか、それぞれで保存しとくと楽。

f:id:irof:20210215110907p:plain

ここまでは SPRING_PROFILES_ACTIVE と書いてきましたが、画像のように spring.profiles.active でもいけます。 もちろんVM OptionやProgram argumentに追加しても構いませんが、環境変数に統一と言う方針なのでこちらを使用。 AWS SDKを使用する場合はここに AWS_PROFILE を並べたりします。

これ別にProfile専用ではなく、全部のプロパティがこれでいけるんですよね。Profile用の専用の仕組みだとこれは使えない。 SPRING_APPLICATION_JSON 環境変数使えば一括での設定もできるのもたまに便利。たまに。

固定のProfileを設定するのであれば、それぞれのツールに設定してもいいと思います。ローカルで動作させるならどうせ local を指定するでしょうし。そう言うのなら「毎回必ず指定する」のは無駄なので、build.gradleだのpom.xmlだのに固定で書いておく方がいいと思う。

結論

……なんてものはないんだけど。 やり方はいくらでもあるし、どんなやり方をしてもいいと思います。

私が「個人ブログ」とかに求めるのってこういう「他の人はどうやってるか知らんし、もっと効率いい方法あるだろけど、自分はこんなことやったりするよ」な内容なんだよなーと思いながら書いたやつ。

https://twitter.com/irof/status/1357153181873020930

このブログもそう。

関連

irof.hateblo.jp

正常な状態を保つのは大変

正常な状態を保ち続けるのは大変だと言うことを、保てるようになると簡単に忘れちゃう。

正常な状態?

  • デプロイが成功する。
  • テストが通る。
  • コンパイルが通る。
  • ERRORWARN のログが出ない。
  • ビルドがすぐ終わる。
  • すべての変更がバージョン管理されている。

あげるてるとキリがないけど、ここで言う「正常な状態」がどんなものかは伝わると思う。 何を正常と言うかはその場その時で変わるけれど、少なくともコンパイルできない状態を正常とは言わない。

崩すのは簡単

正常な状態は簡単に崩せる。コンパイルエラーにするためには適当な文字を1文字足すか消すかするだけ。でも誰も崩したくて崩さない。

崩れてしまうことってある

変更途中は崩れるもの。仕方ない。

正常な状態を保つのはコストがかかるし、そこに気を取られると遅くなってしまう。スピードが大事。仕方ない。

なりふり構わずにゴールを目指してる間は崩れててもいい。仕方ない。

……ほんとに仕方ないのかなって。

正常な状態は保てない?

スポーツ選手とかエキスパートのフォームって綺麗だと思うんですよ。 ホームラン打った瞬間のバッターの写真とか、マラソンランナーのフォームとか。

全力でやると姿勢が崩れると言うのは素人なんじゃないかなと思ったりする。

私の感覚

デプロイはいつも成功してて欲しい。環境構築時とかは結構落ちるけれど、安定飛行をはじめたら滅多に落ちない。たまに失敗した時は大事件で大慌てするし、次から起こらないようにテストを作ったりする。

テストが通るのは当たり前。なんだけど、私は結構テストはカジュアルに落とす。TDDerだってのもあるけど、「とりあえずテスト実行してみよう!」とか言ったりして、そんなノリで実行するから当然のように落ちる。むしろテストは落ちてこそ、落ちた時が仕事した時とか思ってたりする。テスト駆動開発を継続するで話した内容……2013年ですね。

かと言って「テストが落ちたままにする」は違くて。テストが落ちたらすぐに直す。正常な状態にする。 テストが落ちる崩れた状態で、他のことをしてさらに崩れたら、正常に戻ったと言えなくなっちゃうから。

コンパイルが通るのは流石に当たり前だよね。 と言いたいんだけれど、コンパイルが通ってない瞬間ってのは案外ある。瞬間を切り取れば。 コンパイルが通っているのが当たり前の人には意外かもしれないけれど、コンパイルエラーが発生した状態でそれを解消せずに他のコードを書き続ける人もいたりする。

冒頭に列挙した「正常な状態」が崩れているのは、姿勢が崩れた状態で走り続けてるように感じる。とても落ち着かない。 プロスポーツ選手の例をみれば、フォームを整えた方がいい成績に結びついている(はず)。 綺麗なフォームだとうまく走れなくて遅くなると言うのは、練習不足とか筋力不足とか、フォームが身に付いていない状態なんだろうなと。

実際のとこ

「正常な状態」が開発速度や精度を向上させていることは、崩された時に初めて感じる。 そう言う時に自分が正常と思っているものがそうでもないって世界があることに気づく。

他の人から見たら私も「それくらいできてないの?」と言われるようなこともあると確信してる。 「正常な状態」が崩れているように見えていても、崩れている側はそもそも「正常な状態」を認識できていないとかもある。

できる状態が普通になると、意識して取り組んでいるうちは大変なことを忘れてしまう。 できていない人には大変なのに、できる人には簡単。このギャップが厄介だなぁと思ったり。

どうしたものかなぁって、もやもや。 まとまらないや。

Apache POIのバージョンあげたらサイズ跳ね上がったので調べたログ

Apache POI の久々のメジャーバージョンアップである 5.0.02021-01-20 に出ました。 テストも通るしいっかーと、雑に更新したらexecutable jarのサイズが26MB増えてウケました。(笑いごとじゃない

多少のJarサイズはそれほど問題にならないことが多いのですが、今回はもともと40MB程度のJarで、それでも「ちょっと大きいなー」って思っていたのがいきなり60MBを超えただけに、ちょっと待ってくれって気分です。

そんなわけでちょっとみてみようかーと。見ていった手順をだらだら書きます。参考になるかどうかは知りません。

ちなみにPOIのソース管理は Subversion で、GitHubにあるのはミラーです。 今回ソースは読んでないけど。ライブラリのソースは本体のリポジトリじゃなく、Mavenリポジトリ-sources.jar で読むことの方が多いと思います。でも本体の場所を知ってると、ごく稀にコミットログとか追いたくなったときに便利です。

空のjarを作るプロジェクトを作る

調べるのに他のライブラリが混ざってるとかったるいので、ピュアなプロジェクトを作ります。 ビルドにいちいち時間かかるのも嫌だし。

雑にcatbuild.gradle を書きます。

% cat>build.gradle
plugins { id 'java' }
^C

一つもクラスを作らずにビルドします。

% gradle build

BUILD SUCCESSFUL in 537ms
1 actionable task: 1 up-to-date

できました。 中身は もはや人間が実行することもなくなった jar コマンドでみれます。

% ls -l build/libs
total 8
-rw-r--r--  1 irof  staff  261  2  3 22:43 poi5.jar
% jar -tf build/libs/poi5.jar
META-INF/
META-INF/MANIFEST.MF

MANIFEST.MF だけですね。この手順は「Gradleが動いてる」とかその辺の確認です。すぐ確認できるんだからすぐ確認する。あ、ディレクトリ名を poi5にしたんでその名前になってますね。

jar コマンドがよくわからないなら拡張子を .zip に変えて適当に開いてやればよいです。昔はよくやってました。jarってzipなんで。

poiを入れる

引き続きcatで追記します。

% cat>>build.gradle
repositories.jcenter()
dependencies.implementation 'org.apache.poi:poi:5.0.0', 'org.apache.poi:poi-ooxml:5.0.0'
^C

repositoriesmavenCentralでもいいです。Gradleの次のアップデート以降で非推奨になったりするんだろな。

追記: てか jcenter なくなります。タイムリーすぎてウケた(笑い事じゃない

implementationとかは実務では1artifactずつ並べるんですが、今回は cat で書いてるから滅多に使わない複数列挙をします。 repositoriesとかdependencies とかは普通 {} で書きますが、一つだったらそのまま . で繋げられます。滅多に役に立たない知識。 この書き方は実務ではお勧めしません。

ooxmlとかは ブログの過去ログを検索すれば……10年以上前か。当時使ってたのが3.5なので、長いこと中身読んでないなぁ。。

build.gradle全体

plugins { id 'java' }
repositories.jcenter()
dependencies.implementation 'org.apache.poi:poi:5.0.0', 'org.apache.poi:poi-ooxml:5.0.0'

しんぷる。

依存ツリーを確認

gradle dependencies でみれます。ようするにMavendependency:tree ……なんだけど、IDE上でGUIで見ることの方が多いかなと思います。コマンド知らなくても特に問題ないんじゃないかな。

--configuration 無しだと compileClasspath とか testRuntimeClasspath とか色々でてノイズなので、眺めるときはだいたい runtimeClasspath を使ってます。

% gradle dependencies --configuration runtimeClasspath

> Task :dependencies

------------------------------------------------------------
Root project 'poi5'
------------------------------------------------------------

runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.apache.poi:poi:5.0.0
|    +--- org.slf4j:slf4j-api:1.7.30
|    +--- org.slf4j:jcl-over-slf4j:1.7.30
|    +--- commons-codec:commons-codec:1.15
|    +--- org.apache.commons:commons-collections4:4.4
|    +--- org.apache.commons:commons-math3:3.6.1
|    \--- com.zaxxer:SparseBitSet:1.2
\--- org.apache.poi:poi-ooxml:5.0.0
     +--- org.apache.poi:poi:5.0.0 (*)
     +--- org.apache.poi:poi-ooxml-lite:5.0.0
     |    \--- org.apache.xmlbeans:xmlbeans:4.0.0
     +--- org.apache.commons:commons-compress:1.20
     +--- com.github.virtuald:curvesapi:1.06
     +--- org.bouncycastle:bcpkix-jdk15on:1.68
     |    \--- org.bouncycastle:bcprov-jdk15on:1.68
     +--- org.bouncycastle:bcprov-jdk15on:1.68
     +--- org.apache.santuario:xmlsec:2.2.1
... 略 ...
     +--- org.apache.xmlgraphics:batik-all:1.13
... 略 ...
     \--- de.rototor.pdfbox:graphics2d:0.30
          \--- org.apache.pdfbox:pdfbox:2.0.22
               \--- org.apache.pdfbox:fontbox:2.0.22

削ってるからあれだけど、めっちゃいっぱい出てました。poi-ooxml由来で189個。

詳細は省略しますが、org.apache.xmlgraphics:batik-all で約13MB、 org.bouncycastle で約7MB、 de.rototor.pdfbox:graphics2d で約5MB。これらで約23MBになります。増加分とほぼ合いますね。

Apache BatikSVGを扱うもの。Bouncy Castleは暗号化。最後のはApache PDFBoxのアダプタなんでPDFを扱うものです。 なるほどこれらに対応したのかーと言う感じですね。POI使う時に制約になった記憶がうっすらあります。うっすら。(でも今回はシンプルな xlsx 扱うだけなんで使わない……)

リリースノートを確認

最初にやれ←

なるほど(わからん

ちなみに poi-ooxml-lite とかがあって、これ使うといいんじゃ?と思いましたが、こちらは元 ooxml-schemas と書いてるようにスキーマだけ。使いたい XSSFWorkbook とかは入っておらずでした。

ちなみに前バージョン

% gradle dependencies --configuration runtimeClasspath

> Task :dependencies

------------------------------------------------------------
Root project 'poi5'
------------------------------------------------------------

runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.apache.poi:poi:4.1.2
|    +--- commons-codec:commons-codec:1.13
|    +--- org.apache.commons:commons-collections4:4.4
|    +--- org.apache.commons:commons-math3:3.6.1
|    \--- com.zaxxer:SparseBitSet:1.2
\--- org.apache.poi:poi-ooxml:4.1.2
     +--- org.apache.poi:poi:4.1.2 (*)
     +--- org.apache.poi:poi-ooxml-schemas:4.1.2
     |    \--- org.apache.xmlbeans:xmlbeans:3.1.0
     +--- org.apache.commons:commons-compress:1.19
     \--- com.github.virtuald:curvesapi:1.06

こっちは省略なしです。シンプルなものですね。

上でサイズの大半を占めてるって書いてたライブラリはこっちでも使ってないし exclude しちゃっていい気がする。 ってことで外して、テスト通って、OK。と言う感じ。

注意と昔話

Mavencompile スコープで宣言されるものを外すのは危険です。自信とそれを裏付ける自動テストがなければやめましょう。(とか言いながら自信はなかったりする。) ライブラリの依存ライブラリがないと、実行時にクラスねーよって落ち方します。コンパイルチェックが効かない。せっかくコンパイル言語使ってるのにこれが起こるのはダサい。

Maven以前は自身でJarを各ライブラリの配布サイトからかき集めてクラスパスに追加する作業が必要で、この時に集めるのが漏れて実行時エラーとかは結構よくある話でした。 でも現代ではこの苦労をする必要はありません。それがMavenが変えたJavaの世界です。

自動で入る依存ライブラリを外すのは、解決済みの問題のトリガーを自身で引く行為です。 必要な場合に、ある程度(完全ではない)安全に行うには自動テストが必須です。完全じゃないってのは肝に銘じた上で。