まぁ便利ですよね、Profiles。アプリケーションの環境差分を吸収してくれる仕組みで、開発者のローカル環境、CI環境、ステージング環境、本番環境みたいな各環境での設定の差分を切り替える仕組みです。同じアプリケーション資材を使いつつも各環境での動作の違いを制御することができるアレですね。Spring Bootの機能も色々ありますがその中でもかなり利便性の高い機能ではないかと思います。
前提バージョン
今回の記事は以下のバージョンを前提にしています。
- Spring Boot:2.0.3.RELEASE
- kotlin:1.2.51
切替えられるもの
大きくいうとプロパティファイル(application.properties
/application.yml
)とBean定義を各環境で切り替えることが出来ます。Spring Bootでは様々な設定がプロパティの設定で制御可能なので、かなりの範囲の設定を環境依存値として使用することが出来ますね。
プロパティファイルの切替
Spring Bootでは、クラスパスルートのapplication-[Profile].propertiesまたはapplication-[Profile].ymlが有効になります。Profileなしのapplication.properties
は常に有効であるため、環境毎に異なる設定値のみを各Profileのプロパティに記載することで、環境差分を吸収することが出来ます。
. `-- src `-- main `-- resources |-- application-development.properties `-- application.properties
例えばこんな感じにプロパティファイルを配置しておけば、development
プロファイルを有効にした場合にapplication-development.properties
が有効になる、といった感じですね。
前述の通り、プロパティファイルで切り替えられる設定は多岐に渡り、各リソースのチューニング項目はもちろん、ログ出力パス・ログレベルやSpring Boot Actuatorのエンドポイントの制御など、柔軟な設定が可能です。
Bean定義の切替
Profilesを用いて環境毎にBean定義を切り替える事ができます。やり方は@Profile
アノテーションを付与してプロファイルを指定するだけです。
@Bean @Profile("development") fun dataSource(): DataSource { return EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .build() }
この例ではProfilesにdevelopment
が指定された場合にDataSource
がBean定義される例を示しています。Java Configでは柔軟なBean定義が可能なため、設定ファイルのみでは難しいものも環境差分の設定として扱うことが出来ますね。
Profilesの指定方法
Spring BootにおけるProfilesの指定方法はテスト実行時の制御も含めてたくさんあります。詳細はリファレンスをご確認ください。
テスト時の指定は除いてよく使われるものとしては、
- コマンドライン引数(
--spring.profiles.active
) - JVMオプション(
-Dspring.profiles.active
) - 環境変数(
SPRING_PROFILES_ACTIVE
)
辺りでしょうか(上から優先)。環境構築の方針等によって都合の良いものを選択すれば良いと思います。
-Dspring.profiles.active=profileA,profileB
と言った形。アプリケーションでの参照方法
指定されているProfilesが何なのかをアプリケーションから参照することも可能です。ここでは、指定されているProfilesを取得するREST-APIを作成してみましょう。
@RestController("profiles") class ProfileRestController(val environment: Environment) { @GetMapping fun getProfile(): Array { return environment.activeProfiles } }
インジェクションしたEnvironment
から指定されているProfilesを取得しています。実行してみるとこんな感じになります。
まずはProfiles指定無しで起動。
java -jar ./build/libs/spring-profiles-sample-0.0.1-SNAPSHOT.jar
Profilesの指定をしていないため、defaultが指定されます。
2018-07-28 20:37:00.317 INFO 20768 --- [ main] c.e.s.SpringProfilesSampleApplicationKt : No active profile set, falling back to default profiles: default
APIを実行してみます。
$ curl -H 'Content-Type:application/json' http://localhost:8080/profiles % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 2 0 2 0 0 11 0 --:--:-- --:--:-- --:--:-- 11[]
何も帰って来ません。
続いてdevelopmentを指定して起動。
java -Dspring.profiles.active=development -jar ./build/libs/spring-profiles-sample-0.0.1-SNAPSHOT.jar
Profilesにdevelopmentが指定されている旨が表示されます。
2018-07-28 20:43:22.612 INFO 21176 --- [ main] c.e.s.SpringProfilesSampleApplicationKt : The following profiles are active: development
APIを実行してみます。
$ curl -H 'Content-Type:application/json' http://localhost:8080/profiles % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 15 0 15 0 0 95 0 --:--:-- --:--:-- --:--:-- 95["development"]
とまぁこんな感じになります。
Environment
から取得したProfilesをBean定義しておけば○○モードで起動中、といった制御も簡単に出来ますね。
応用的な使い方
基本的なProfilesの制御方法をご紹介しましたが、ちょっとした応用的な使い方もご紹介します。
複雑なProfiles指定
Bean定義の切り替えに使用する@Profile
アノテーションは、有効となるProfilesの指定しか出来ませんが、profileA、profileBともに指定されていない場合に有効、といった指定も可能です。
そのような指定にはCondition
インターフェースの実装クラスと、@Conditional
アノテーションを使用します。
まずはCondition
インターフェースの実装クラスの作成。
class SampleCondition : Condition { override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean { return !context.environment.acceptsProfiles("default", "development") } }
ここでは、default、developmentがともに指定されていない場合にtrue
を返すメソッドを定義しています。このCondition
クラスを@Conditional
アノテーションで判定します。
@Bean @Conditional(SampleCondition::class) fun beanBySampleCondition(): String { return "beanBySampleCondition" }
SampleCondition
のmatches
メソッドがtrue
を返す場合のみBean定義が行われます。もう少し作り込めばより便利なやり方もできそうですね。
テストの実行をProfilesで制御する
Profilesの仕組みを使って環境差分を吸収しつつも、テストコードが実行される環境(開発者のローカル環境とCI環境など)の間で実行できるテストと実行出来ないテストが出てきてしまうような場合は、以下のような方法でテストの実行を制御することも可能です。
@Test @IfProfileValue(name="spring.profiles.active",value = "development") fun developmentTest() { println("Test on development profile.") }
@IfProfileValue
は、name
に指定したプロパティがvalue
に指定した値である場合のみテストが実行されるアノテーションです。これを利用し、指定したProfilesに応じてテストの実行要否を制御することが出来ます。
サンプルアプリケーション
サンプルというほどサンプルではないのですが、説明したコードについてはこちらにあります。
まとめ
もっと突き詰めると工夫できるやり方はたくさんあるのかもしれませんが、今回ご紹介したようなある程度基本的な仕組みを抑えておくととても便利に使えるのでは無いかと思います。
Spring Bootはクラウド環境でのアプリケーションや、マイクロサービスアーキテクチャと相性が良いと言われていますが、オンプレミス環境のWebアプリケーションにおいてもProfilesの仕組みは有用ではないかと思います。
Springを学ぶなら
この辺の本がおすすめ。
この記事に対するコメント