日々常々

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

SpringBootアプリケーションのProfileで制御したいとき

Short Answer

  • @Profile でBeanを変える
  • どうしても評価するなら Environment#acceptsProfiles(Profiles) を使う
  • 文字列では読まない
  • 文字列では読まない
  • 文字列では読まない

説明

SpringBootではProfileを使って設定ファイル( application-{profile}.properties とか)の読み替えを行うことが多いかと思います。 Profileは設定ファイルの切り替えだけじゃなく、と言うか、主にはBeanを切り替える機能です。 SpringBootネイティブな人だと「設定ファイルを変えるためのもの」と認識しているかもしれません。 あながち間違いじゃないです。

さて、掲題のProfileで何かを制御したいとき。処理を変えたい時。 基本的には先にあげたように @Profile を使ってのBean切り替えです。 @Profile@Conditional の仲間で、条件によってBean生成したりしなかったりする子です。

@Profile には基本的にProfile名を書きます。 @Profile("hoge") とかですね。 ですがそれだけではなく、色々な条件、たとえば !hoge & fuga とかが書けます。 詳しくはProfileのドキュメントProfiles#of(String...)のドキュメントを参照ください。 @Profile と他の @Conditional を組み合わせてのBean制御は、頑張ってなんとかできなくはないんですけど、素直に @Profile に寄せるのが吉です。

この @Profile はBeanを制御するアノテーションなんで、@Component なクラスを作るか @Bean なメソッドを作る必要があります。 「ちょっと分岐したいだけなのにいちいちクラス作るなんてとんでもない!」ってのがあるかもしれません。(賛同できませんが。)

そのような、どうしても実行時に評価したいときは、 Environmentをインジェクションするなりなんなりして acceptsProfiles メソッドで評価できます。 名前の通り引数で渡したProfileが有効かを見てくれるメソッドです。 コンテキスト起動時に決まってるものを、どうしてもリクエストごと等の実行時に常に評価したいなら、これを使いましょう。こんな表現してながら私もたまにやってたりします。

背景

こう言うことを書いているのも、よろしくない別解があって。

@Value("${hoge.fuga}") とかでプロパティが読めるのと、実行時に指定する -Dspring.profiles.active=hoge とか環境変数 SPRING_PROFILES_ACTIVE (大文字である必要はないけどなんとなく)から、こんな感じで取れるのが想像できちゃうわけで、これを稀によく見るんですよね……。

@Value("${spring.profiles.active}")
String activeProfile;

if ("hoge".equals(activeProfile)) ...

これはやっちゃいけないです。

Profileが1つだったら期待通り動くとは思います。

でもProfileって複数指定できるんですよね。 "spring.profiles.active" だし。 後からProfile追加したりとかするかもしれない。そうなった時に動かなくなるかもしれない。 String#containsは流石にないよね。カンマでsplitする?正規表現でマッチさせる? ……それっぽくはできるかもしれないけれど、Springがその通り解釈してるかは怪しいところです。 Springが解釈して実際に動いてるProfileと完全に同じでなければ、バグの温床になります。

と言うことで、先頭に書いた通り。 Profile自体の評価はSpringにお任せして、文字列として比較とか独自の評価方法は避けましょう。

誰に言われたでもないけど追記: 2021-01-20T13:00

「Profile1つしか設定しないし別にいいじゃん」みたいなのは、短期的にはその通りなんですけど、いくつか理由があって。

  • @Value("${spring.profiles.active}") と書くと、何もProfileを指定しない(default が設定される)時に「そんなプロパティないよ」ってエラーで起動しなくなります。
    • 空プロパティを定義するとか ${spring.profiles.active:} みたいな書き方したら回避できるけど、ダサいよね。
  • 継続開発の中で「Profileを増やして対応しよう」と言う手段が取れなくなる
    • Springの経験のある人が後から参画した際に「Springならこれが使えるはず」が通じなくなってしまいます。
    • こう言うのが増えると「Springを使っている」じゃなく、独自フレームワークを使ってるのと同義になってしまいます。つらい。

後者が大きいかな。「よく使われているフレームワークを使用する」のメリットを活かせる開発をしてくほうが、きっといいことあります。

余談

先頭には書きませんでしたが、Environment#acceptsProfiles(Profiles) で分岐するのではなく、 application-{Profile}.properties とかに何かしらの制御用のプロパティを定義して、Profileではなくそっちで制御するとかもあります。 Environment のようなSpringの低レベルなAPI使うより、こっちの方がいい場合もあります。 プロパティファイルの構成とか、アプリケーションの作りとかに合わせていい感じにいきましょう。

蛇足

実際にProfileがどう解決されてるかは追ってみると楽しいかもしれません。 追加されたりマージされたりしていて、おそらく想像以上のことをやってます。 たとえば default がどこで入ってるか、とか。楽しいですよね。(賛同が得られるとは思っていない

SpringBootのプロジェクトを作成する

2020-12-29 時点で私がどうやっているかって言うの。 色々やり方あるし、他でも書いた記憶あるけど、現時点のスナップショットを書いておきます。

必要なもの

以下が実行できること

  • curl
  • gradle
    • 私は SDKMAN で入れてます
    • gradle の実行にJDKいるけど、JDKは入ってるでしょ←
  • idea

やること

curl -O https://start.spring.io/build.gradle
gradle wrapper
idea .

こんだけ。以下は解説とかおまけとか。

やってること

curl で叩いてるのは Spring Initializr です。 SpringBootの雛形を作成してくれるWebサービス。必要なライブラリとかを -d dependencies=web,actuator とかで指定できるんだけど、それはあまり使わなかったり。 build.gradleの最終形までSpring Initializrでできるわけではないし、ここでコマンドで入力するより build.gradle を後で直接編集するほうが早いし、まぁ色々。 用途が限定的なデモの手順やWebUIでやるなら選んでいいと思います。

普通(?)に使うとプロジェクトの一式が入ったzipファイルを作ってくれるんだけど、私が欲しいのは build.gradle だけなので、パスで指定してます。正直このくらいは自分で書いてもいいし、前はそうしてたけど、SpringBootの最新バージョンいくつだっけーとか確認するのも面倒になったし、そこそこいい感じ(例えば SpringBoot2.3.xだと SpringBoot2.2でJUnit5がデフォルトになったのでbuild.gradleを書き換える で書いたようなspring-boot-testからjunit-vintageのexcludeをやってくれる。2.4で要らなくなったけど。)のbuild.gradleにしてくれるので使ってます。

次にGradle Wrapperの生成。ローカルにインストールしているバージョンのGradleで作ります。 Spring Initializrからダウンロードしたらgradlewが入ってるんだけど、バージョンの更新がワンテンポ遅れたりとか、SpringにGradleのバージョンが依存するのは私的に依存が逆なので、コントロールできる手元のGradleでやってます。 バージョンズレでSpring Initializrの作ってくれるbuild.gradleが読めないとかだと使えないのだけど、Gradleも後方互換かなり強いので大丈夫かなって。 Spring Initializrからダウンロードしようと思ったらzipダウンロードからの展開になるし、入ってるののバージョン確認しなきゃだし、まぁ色々。用途が限定的なデモの手順や(以下略

最後にIDEAを起動して、IDEAにGradleを使ってjarのダウンロードとか、プロジェクトの準備をしてもらいます。

そのあとやること

IDEAが起動したら @SpringBootApplication なメインクラスを作ります。 これもSpring Initializrが作ってくれるのとほぼ同じになりますが、コーディングの肩慣らし的な意味もあります。 IDEが元気に動いてるのかとか、メインクラスの名前をどうするのかとか、パッケージをどうするのかとか。この辺りもSpring Initializrで指定して生成できますが、コマンドラインとかWebUIとかで入力する気にはならないんだ。用途が限定的な(以下略

作ったらとりあえず起動。動くかの確認は確実に動くと確信しているところで行います。 このタイミングで「なぜか起動できない」とかも稀によく起こります。JDKいろんなバージョン入れてるもんだから、たまにIDEAが使うのを消しちゃってたり。起こったことがない人は、運がいいか、きっちりしてるか。私の適当さはこれくらいです。

起動したら @SpringBootTest なテストクラスと空っぽの @Test メソッドを作ります。 Spring Initializrで生成できるのと一緒。まぁメインクラスからテストクラスをIDEAに作ってアノテーションつけるくらいなんで、3秒くらいです。 これは他のテストが動き次第、消したり消さなかったり。この手のテストを自動生成すると消せなくなりがちなので、何のためのテストかを認識しながら作るのがいいと思ってます。

やったりやらなかったり、だいたいやること

Spring Initializrで生成するbuild.gradleはGradleプラグインio.spring.dependency-management で依存ライブラリのバージョンを制御しています。 これをGradle5.2以降で使える platform を使うように変更します。 そのままでも害はないのですが、SpringBoot以外のBOMを使うこともあります。そう言う時に複数の制御方法があるのは避けたいところ。 混在したことによる問題に遭遇したことはないんですが、もし遭遇したらすっごい面倒なことになると思うんですよね……。 意図しないバージョンのライブラリが入った時に解析する方法は少ないほうがいいと思っています。

build.gradle の片付けを終えたら、必要に応じて依存関係を足していきます。 この辺は追加したいライブラリの公式サイトを見たり見なかったりしたあと、なんだかんだでMaven Centralを検索してます。

こんな感じ。

GitHubで複数メールアドレスを登録している時の設定と使われ方

ただのメモというか、現時点の挙動です。ドキュメントのたぶんこの辺 とかちゃんと読んだら書いてると思うけど。

メールアドレス一個だけ登録してる人(大多数がそうだと思う)には全く関係のない話。

複数メールアドレスを登録する意味

GitHubでコミットをユーザーに紐づけるのにGitのメールアドレス( git configuser.email )が使われる。 コミットログとかをWebUIでみた時にアイコンが表示されたりユーザーページにリンクされたりするあの機能。 登録していないメールアドレスを使うと、知らない人のコミットって感じになってしまう。GitHubを使うならコミットしうるGitのメールアドレスを登録しておくのが良さげ。 複数のメールアドレスでコミットするとかってそんなにない気もするけれど。(私はしてるけど)

Gitのユーザー名 user.name は使ってないのなんでだろうって思ったことあるけど、ユーザー名ってGitHubのユーザー名とGitのユーザー名を一緒にできないこともままありそうだし、突き合わせるのには向いてなさそうね。メールアドレスが妥当ぽい。

<脱線> あれ、もしかして草ってこれで生やしてる? 他の人のメールアドレスを勝手に使ったコミットで埋めた公開リポジトリを作ったら、無理矢理草を生やせたりするんじゃないだろうか……やらんけど。 </脱線>

Public profile の Public email

f:id:irof:20201130025059p:plain

GitHubのプロフィールに表示される。 「GitHub見ました!」ってメールはここに来る感じ。あのメールってGitHubのプロフィールに表示しているメールアドレスは見てるのかもしれないけど、リポジトリの内容ってほとんど見てないよね……。

マージボタンをおしたとき出るもの

f:id:irof:20201130025518p:plain

どのメールアドレスのコミットにするかを聞いてくれる。 この選択肢は Emails に登録しているもの。

順番は辞書順?GitHubにメールアドレスを登録した順でもなさげ。使いたいのが一番上にきてたから意識してなかった……

設定変えて試してみたけど、どうもpublicとかprimaryとかが一番上に来るわけではなさそう。 ドキュメントには

"Primary email address(プライマリメールアドレス)"リストで、WebベースのGit操作と関連づけたいメールアドレスを選択してください。

って書いてるんだけどなぁ。

APIでマージした時に使われるもの

IDEAのGitHubプラグインとかGitHub CLIとかでPRをマージした時。 つまるところPersonal access tokensを使用した場合なんだけど、これで使われるのは Emails でPrimaryにしているもの。こんな表示がされてる。

f:id:irof:20201130031643p:plain

なんでこれ書いてるかっていうと

GitHub CLIでマージしてみたら予期しないメールアドレスになっちゃったので。事故ですね。まぁいいけど。 てっきりPublic emailが使われると思ってたんよねー。WebUIでマージする時に特に意識しなくてもそうなってたから……どうもそれも偶然くさい。 Primary設定しなおした。